diff --git a/backend/Dockerfile b/backend/Dockerfile
new file mode 100644
index 00000000..b791b4e3
--- /dev/null
+++ b/backend/Dockerfile
@@ -0,0 +1,16 @@
+FROM golang:1.19
+LABEL authors="marka"
+
+WORKDIR /backend
+
+COPY go.mod go.sum ./
+RUN go mod download
+
+COPY . .
+
+RUN go build -o backend .
+
+EXPOSE 8080
+
+CMD ["./backend"]
+# docker run -p 8080:8080 backend
\ No newline at end of file
diff --git a/backend/README.md b/backend/README.md
index 5541b8ad..ff4d84c8 100644
--- a/backend/README.md
+++ b/backend/README.md
@@ -89,15 +89,34 @@ Obtener los detalles públicos de un usuario, dado su correo
``` json
{
"status": 200,
- "message": "User found",
+ "message": "Student found",
"data": {
- "empresa": {
- "id_empresa": "prueba@prueba",
- "nombre": "pruebaEmpresa",
- "foto": "foto",
- "detalles": "empresa de prueba",
- "correo": "prueba@prueba",
- "telefono": "12344433"
+ "student": {
+ "correo": "estudiante@prueba.com",
+ "nombre": "Estudiante Actualizado",
+ "apellido": "Prueba",
+ "nacimiento": "2002-02-02T00:00:00Z",
+ "telefono": "12345678",
+ "carrera": 1,
+ "semestre": 4,
+ "cv": "",
+ "foto": "",
+ "universidad": "Universidad Del Valle de Guatemala"
+ }
+ }
+}
+```
+
+``` json
+{
+ "status": 200,
+ "message": "Enterprise found",
+ "data": {
+ "company": {
+ "correo": "empresa@prueba.com",
+ "nombre": "Empresa de Prueba",
+ "foto": "",
+ "detalles": "Detalles de Prueba"
}
}
}
@@ -352,6 +371,9 @@ Crea una oferta de trabajo
"requisitos" : "string"
"salario" : "double"
"id_carreras" : "[]string"
+ "jornada" : "string"
+ "hora_inicio" : "time"
+ "hora_fin" : "time"
}
```
@@ -378,6 +400,9 @@ Actualiza una oferta de trabajo
"requisitos" : "string"
"salario" : "double"
"id_carreras" : "[]string"
+ "jornada" : "string"
+ "hora_inicio" : "time"
+ "hora_fin" : "time"
}
```
@@ -407,28 +432,50 @@ Devuelve la información para las preview de las ofertas disponibles
"nombre_carreras": "Ingenieria en Sistemas",
"nombre_empresa": "Valve Corporation",
"puesto": "Desarrollador de Videojuegos",
- "salario": 15000
+ "salario": 15000,
+ "jornada": "Tiempo completo",
+ "hora_inicio": "0000-01-01T17:00:00Z",
+ "hora_fin": "0000-01-01T17:00:00Z"
},
{
"id_oferta": 4,
"nombre_carreras": "Ingenieria en Sistemas",
"nombre_empresa": "Simán",
"puesto": "DataBase Administrator",
- "salario": 10000
+ "salario": 10000,
+ "jornada": "Tiempo completo",
+ "hora_inicio": "0000-01-01T17:00:00Z",
+ "hora_fin": "0000-01-01T17:00:00Z"
},
{
"id_oferta": 1,
- "nombre_carreras": "Ingenieria en Sistemas, Ingenieria en mecánica industrial",
+ "nombre_carreras": "Ingenieria en Sistemas",
"nombre_empresa": "Empresa INC",
"puesto": "Desarrollador Web Junior",
- "salario": 5000
+ "salario": 5000,
+ "jornada": "Tiempo completo",
+ "hora_inicio": "0000-01-01T17:00:00Z",
+ "hora_fin": "0000-01-01T17:00:00Z"
+ },
+ {
+ "id_oferta": 1,
+ "nombre_carreras": "Ingenieria en mecánica industrial",
+ "nombre_empresa": "Empresa INC",
+ "puesto": "Desarrollador Web Junior",
+ "salario": 5000,
+ "jornada": "Tiempo completo",
+ "hora_inicio": "0000-01-01T17:00:00Z",
+ "hora_fin": "0000-01-01T17:00:00Z"
},
{
"id_oferta": 2,
"nombre_carreras": "Ingenieria en Sistemas",
"nombre_empresa": "Empresa INC",
"puesto": "Desarrollador Full Stack",
- "salario": 10000
+ "salario": 10000,
+ "jornada": "Tiempo completo",
+ "hora_inicio": "0000-01-01T17:00:00Z",
+ "hora_fin": "0000-01-01T17:00:00Z"
}
]
}
@@ -465,6 +512,9 @@ Devuelve las ofertas de trabajo publicadas por una compañia
2,
3
]
+ "jornada": "Tiempo completo",
+ "hora_inicio": "0000-01-01T17:00:00Z",
+ "hora_fin": "0000-01-01T17:00:00Z"
},
{
"id_oferta": 60,
@@ -473,6 +523,9 @@ Devuelve las ofertas de trabajo publicadas por una compañia
"descripcion": "{\"ops\":[{\"insert\":\"Puesto Dummy\"},{\"attributes\":{\"align\":\"center\"},\"insert\":\"\\n\"}]}",
"requisitos": "requisitos dummy",
"id_carreras": null
+ "jornada": "Tiempo completo",
+ "hora_inicio": "0000-01-01T17:00:00Z",
+ "hora_fin": "0000-01-01T17:00:00Z"
}
]
}
@@ -511,6 +564,9 @@ Devuelve todos los detalles de una oferta según el ID. Devuelve además la info
"descripcion": "Desarrollador web junior encargado de Diseñar, desarrollar, dar mantenimiento y soporte a las aplicaciones web",
"requisitos": "Conocimientos en HTML, CSS, Javascript, PHP, MySQL, React, NodeJS",
"salario": 5000
+ "jornada": "Medio Tiempo",
+ "hora_inicio": "0000-01-01T08:00:00Z",
+ "hora_fin": "0000-01-01T12:00:00Z"
}
}
}
@@ -524,18 +580,11 @@ Devuelve todos los detalles de una oferta según el ID. Devuelve además la info
"Data": "nil"
}
```
-### [DELETE] api/offers/
-Elimina una oferta de trabajo. También elimina cualquier postulación asociada a la oferta
+### [DELETE] api/offers/?id_oferta=579
+Elimina una oferta de trabajo. También elimina cualquier postulación asociada a la oferta. Se pasa el id de la oferta como query param
> **Note**
> Auth required
-#### Params
-``` json
-{
- "id_oferta" : int
-}
-```
-
#### Response
``` json
{
@@ -700,27 +749,6 @@ Elimina una postulación. El usuario se obtiene del token. Se pasa el id de la p
---
## Administradores
-### [POST] api/admins
-Crea un administrador
-
-#### Params
-
-``` json
-{
- "id_administrador" : "string"
- "nombre" : "string"
- "apellido" : "string"
-}
-```
-
-#### Response
-``` json
-{
- "Status": "200",
- "Message": "Admin Created Successfully",
- "Data": "nil"
-}
-```
### [GET] api/admins/students
Retorna información de estudiantes para el panel de administradores
@@ -771,7 +799,7 @@ Retorna información de empresas para el panel de administradores
```
### [POST] api/admins/suspend
-Suspende un usuario
+Suspende un usuario. Si "suspender" es true, suspende al usuario. Si es false, lo reactiva
> **Note**
> Auth required
@@ -792,3 +820,453 @@ Suspende un usuario
"data": null
}
```
+
+### [DELETE] api/admins/delete/offers?id_oferta=220
+Elimina una oferta de trabajo. También elimina cualquier postulación asociada a la oferta
+
+> **Note**
+> Auth required
+
+#### Params
+Query param
+
+- "id_oferta": int
+
+#### Response
+``` json
+{
+ "status": 200,
+ "message": "Offer deleted successfully",
+ "data": null
+}
+```
+
+### [POST] api/admins/delete/user?usuario=estudiante@eliminar.com
+Elimina un usuario. Elimina toda información asociada al usuario
+
+> **Note**
+> Auth required
+
+#### Params
+Query param
+
+- "usuario": string
+
+#### Response
+``` json
+{
+ "status": 200,
+ "message": "User deleted successfully",
+ "data": null
+}
+```
+
+### [DELETE] api/admins/postulation?id_postulacion=1
+Elimina la postulación de un estudiante a una oferta.
+
+> **Note**
+> Auth required
+
+#### Params
+Query param
+
+- "id_postulacion": int
+
+#### Response
+``` json
+{
+ "status": 200,
+ "message": "Postulation deleted successfully",
+ "data": null
+}
+```
+
+### [POST] api/admins/details
+Obtener los detalles de administrador de un usuario, dado su correo
+> **Note**
+> Auth required
+
+#### Params
+``` json
+{
+ "correo": "string"
+}
+```
+
+#### Response
+``` json
+{
+ "status": 200,
+ "message": "Enterprise found",
+ "data": {
+ "company": {
+ "correo": "empresa@prueba.com",
+ "nombre": "Empresa de Prueba",
+ "foto": "empresa_8900995120.jpg",
+ "detalles": "Detalles de Prueba",
+ "suspendido": false
+ }
+ }
+}
+```
+
+``` json
+{
+ "status": 200,
+ "message": "Student found",
+ "data": {
+ "student": {
+ "correo": "estudiante@prueba.com",
+ "nombre": "Estudiante Actualizado",
+ "apellido": "Prueba",
+ "nacimiento": "2002-02-02T00:00:00Z",
+ "telefono": "12345678",
+ "carrera": 1,
+ "semestre": 4,
+ "cv": "",
+ "foto": "",
+ "universidad": "Universidad Del Valle de Guatemala",
+ "suspendido": false
+ }
+ }
+}
+```
+### [POST] api/admins/postulations?id_estudiante=prueba@prueba
+Devuelve las postulaciones de un Estudiante.
+> **Note**
+> Auth required
+
+#### Params
+``` json
+{
+ "id_estudiante": "string"
+}
+```
+
+#### Response
+```json
+{
+ "status": 200,
+ "message": "Postulations retrieved successfully",
+ "data": {
+ "postulations": [
+ {
+ "id_usuario": "",
+ "nombre": "Javier Alejandro",
+ "apellido": "Azurdia",
+ "id_postulacion": 242,
+ "id_oferta": 402,
+ "estado": "enviada"
+ },
+ {
+ "id_usuario": "",
+ "nombre": "Javier Alejandro",
+ "apellido": "Azurdia",
+ "id_postulacion": 244,
+ "id_oferta": 460,
+ "estado": "enviada"
+ },
+ {
+ "id_usuario": "",
+ "nombre": "Javier Alejandro",
+ "apellido": "Azurdia",
+ "id_postulacion": 276,
+ "id_oferta": 512,
+ "estado": "Enviada"
+ }
+ ]
+ }
+}
+
+```
+
+
+
+---
+
+# File Server
+## Fotos de perfil
+
+### [PUT] /api/users/upload
+Sube una foto de perfil de un usuario. El usuario se obtiene del token.
+El nombre del archivo no es relevante, usando el usuario del token se genera un nombre único para el archivo.
+> **Note**
+> Auth required
+
+Header
+``` json
+{
+ "Content-Type": "multipart/form-data"
+}
+```
+
+Response
+``` json
+{
+ "status": 200,
+ "message": "File uploaded successfully",
+ "data": {
+ "filename": "estudiante_2505480089.jpg"
+ }
+}
+```
+
+### [GET] /api/uploads/:filename
+Devuelve una foto de perfil de un usuario. El nombre del archivo se obtiene en el **query parameter** de la url.
+
+Esto retorna directamente la imagen, no un json. Se puede usar en un tag img de html.
+
+### CÓDIGO DE DEMOSTRACIÓN
+``` html
+
+
+
+
+
+ PFP Upload Test
+
+
+ PFP Upload Test
+
+
+
+
+
+
+
+ Obtener URL del Archivo
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## CVs
+
+### [PUT] /api/students/update/cv
+Sube un CV de un estudiante. El usuario se obtiene del token. El nombre del archivo no es relevante,
+usando el usuario del token se genera un nombre único para el archivo.
+
+> **Note**
+> Auth required
+
+Header
+``` json
+{
+ "Content-Type": "multipart/form-data"
+}
+```
+
+Response
+``` json
+{
+ "status": 200,
+ "message": "File uploaded successfully",
+ "data": {
+ "filename":"estudiante_2505480089.pdf"
+ }
+}
+```
+
+### [GET] /api/cv/:filename
+Devuelve un CV de un estudiante. El nombre del archivo se obtiene en el **query parameter** de la url.
+
+Esto retorna directamente la el PDF, no un json. Se puede usar en un tag embed de html.
+
+### CÓDIGO DE DEMOSTRACIÓN
+``` html
+
+
+
+
+
+ PDF Upload Test
+
+
+ PDF Upload Test
+
+
+
+
+
+
+
+ Obtener URL del Archivo
+
+
+
+
+
+
+
+
+```
\ No newline at end of file
diff --git a/backend/controllers/admin.go b/backend/controllers/admin.go
index c66a6fce..e966e3e0 100644
--- a/backend/controllers/admin.go
+++ b/backend/controllers/admin.go
@@ -4,67 +4,60 @@ import (
"backend/configs"
"backend/models"
"backend/responses"
+ "backend/utils"
+ "fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
-type AdministradorInput struct {
- IdAdministrador string `json:"id_administrador"`
- Nombre string `json:"nombre"`
- Apellido string `json:"apellido"`
+type EstudianteGetAdmin struct {
+ IdEstudiante string `json:"id_estudiante"`
+ Nombre string `json:"nombre"`
+ Apellido string `json:"apellido"`
+ Nacimiento time.Time `json:"nacimiento"`
+ Foto string `json:"foto"`
+ Suspendido bool `json:"suspendido"`
}
-func NewAdmin(c *gin.Context) {
- var input AdministradorInput
+func IsAdmin(c *gin.Context) error {
+ // Solo retorna un error si el usuario no es un administrador
+ role, err := utils.TokenExtractRole(c)
- if err := c.ShouldBindJSON(&input); err != nil {
- c.JSON(http.StatusBadRequest, responses.StandardResponse{
- Status: 400,
- Message: "Error binding JSON: " + err.Error(),
- Data: nil,
- })
- return
+ if err != nil {
+ return fmt.Errorf("error getting role from token: %s", err.Error())
}
- a := models.Administrador{
- IdAdministrador: input.IdAdministrador,
- Nombre: input.Nombre,
- Apellido: input.Apellido,
+ if role != "admin" {
+ user, err := utils.TokenExtractUsername(c)
+
+ if err != nil {
+ return fmt.Errorf("error getting username from token: %s", err.Error())
+ }
+
+ return fmt.Errorf("user '%s' is not an admin", user)
}
+ fmt.Println("Es admin")
+ return nil
+}
- err := configs.DB.Create(&a).Error // Se agrega el administrador a la base de datos
+func AdminGetStudents(c *gin.Context) {
+ var estudiantes []EstudianteGetAdmin
+
+ err := IsAdmin(c)
if err != nil {
c.JSON(http.StatusBadRequest, responses.StandardResponse{
- Status: 400,
- Message: "Error creating. " + err.Error(),
+ Status: http.StatusBadRequest,
+ Message: "Error getting privileges: " + err.Error(),
Data: nil,
})
return
}
- c.JSON(http.StatusOK, responses.StandardResponse{
- Status: 200,
- Message: "Admin Created Successfully",
- Data: nil,
- })
-}
-
-type EstudianteGetAdmin struct {
- IdEstudiante string `json:"id_estudiante"`
- Nombre string `json:"nombre"`
- Apellido string `json:"apellido"`
- Nacimiento time.Time `json:"nacimiento"`
- Suspendido bool `json:"suspendido"`
-}
-
-func GetStudents(c *gin.Context) {
- var estudiantes []EstudianteGetAdmin
-
// Realiza la consulta para obtener la información de los estudiantes con la suspensión
- err := configs.DB.Table("estudiante e").
- Select("e.id_estudiante, e.nombre, e.apellido, e.nacimiento, u.suspendido").
+ err = configs.DB.Table("estudiante e").
+ Select("e.id_estudiante, e.nombre, e.apellido, e.nacimiento, e.foto, u.suspendido").
Joins("INNER JOIN usuario u ON e.id_estudiante = u.usuario").
Scan(&estudiantes).Error
@@ -81,7 +74,7 @@ func GetStudents(c *gin.Context) {
messageMap := map[string]interface{}{"studets": estudiantes}
c.JSON(http.StatusOK, responses.StandardResponse{
- Status: 200,
+ Status: http.StatusOK,
Message: "Students Retrieved Successfully",
Data: messageMap,
})
@@ -92,21 +85,33 @@ type EmpresaGetAdmin struct {
Nombre string `json:"nombre"`
Detalles string `json:"detalles"`
Telefono string `json:"telefono"`
+ Foto string `json:"foto"`
Suspendido bool `json:"suspendido"`
}
-func GetCompanies(c *gin.Context) {
+func AdminGetCompanies(c *gin.Context) {
var empresas []EmpresaGetAdmin
+ err := IsAdmin(c)
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Error getting privileges: " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
// Realiza la consulta para obtener la información de las empresas con la suspensión
- err := configs.DB.Table("empresa e").
- Select("e.id_empresa, e.nombre, e.detalles, e.telefono, u.suspendido").
+ err = configs.DB.Table("empresa e").
+ Select("e.id_empresa, e.nombre, e.detalles, e.telefono, e.foto, u.suspendido").
Joins("INNER JOIN usuario u ON e.id_empresa = u.usuario").
Scan(&empresas).Error
if err != nil {
c.JSON(http.StatusBadRequest, responses.StandardResponse{
- Status: 400,
+ Status: http.StatusBadRequest,
Message: "Error retrieving companies: " + err.Error(),
Data: nil,
})
@@ -117,7 +122,7 @@ func GetCompanies(c *gin.Context) {
messageMap := map[string]interface{}{"companies": empresas}
c.JSON(http.StatusOK, responses.StandardResponse{
- Status: 200,
+ Status: http.StatusOK,
Message: "Companies Retrieved Successfully",
Data: messageMap,
})
@@ -128,24 +133,55 @@ type SuspendAccountInput struct {
Suspender bool `json:"suspender"` // True para suspender, false para reactivar
}
-func SuspendAccount(c *gin.Context) {
+func AdminSuspendAccount(c *gin.Context) {
var input SuspendAccountInput
+ err := IsAdmin(c)
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Error getting privileges: " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
if err := c.ShouldBindJSON(&input); err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
- Message: "Error binding JSON: " + err.Error(),
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Invalid input. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ roleToBeSuspended, err := RoleFromUser(models.Usuario{Usuario: input.IdUsuario})
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Error getting role from the user to be suspended. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ if roleToBeSuspended == "admin" {
+ c.JSON(http.StatusForbidden, responses.StandardResponse{
+ Status: http.StatusForbidden,
+ Message: "Cannot suspend an admin account",
Data: nil,
})
return
}
// Realiza la consulta para obtener la información de las empresas con la suspensión
- err := configs.DB.Model(&models.Usuario{}).Where("usuario = ?", input.IdUsuario).Update("suspendido", input.Suspender).Error
+ err = configs.DB.Model(&models.Usuario{}).Where("usuario = ?", input.IdUsuario).Update("suspendido", input.Suspender).Error
if err != nil {
c.JSON(http.StatusBadRequest, responses.StandardResponse{
- Status: 400,
+ Status: http.StatusBadRequest,
Message: "Error suspending account: " + err.Error(),
Data: nil,
})
@@ -154,7 +190,7 @@ func SuspendAccount(c *gin.Context) {
if input.Suspender {
c.JSON(http.StatusOK, responses.StandardResponse{
- Status: 200,
+ Status: http.StatusOK,
Message: "Account Suspended Successfully",
Data: nil,
})
@@ -162,7 +198,7 @@ func SuspendAccount(c *gin.Context) {
}
c.JSON(http.StatusOK, responses.StandardResponse{
- Status: 200,
+ Status: http.StatusOK,
Message: "Account Reactivated Successfully",
Data: nil,
})
@@ -178,47 +214,329 @@ type Offer struct {
IdCarreras []string `json:"id_carreras"`
}
-func DeleteOfferAdmin(c *gin.Context) {
+func AdminDeleteOffer(c *gin.Context) {
// con IDOferta del struct Offer, se elimina la oferta por medio de un query.
idOferta := c.Query("id_oferta")
- err := configs.DB.Where("id_oferta = ?", idOferta).Delete(&models.Oferta{}).Error
+ err := IsAdmin(c)
if err != nil {
c.JSON(http.StatusBadRequest, responses.StandardResponse{
- Status: 400,
- Message: "Error deleting offer: " + err.Error(),
+ Status: http.StatusBadRequest,
+ Message: "Error getting privileges: " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ err = configs.DB.Where("id_oferta = ?", idOferta).Delete(&models.Oferta{}).Error
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Error deleting offer. " + err.Error(),
Data: nil,
})
return
}
c.JSON(http.StatusOK, responses.StandardResponse{
- Status: 200,
+ Status: http.StatusOK,
Message: "Offer deleted successfully",
Data: nil,
})
}
-func DeleteUsuario(c *gin.Context) {
+func AdminDeletePostulation(c *gin.Context) {
+ // con IDOferta del struct Offer, se elimina la oferta por medio de un query.
+ idPostulacion := c.Query("id_postulacion")
+
+ err := IsAdmin(c)
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Error getting privileges. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ err = configs.DB.Where("id_postulacion = ?", idPostulacion).Delete(&models.Postulacion{}).Error
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Error deleting postulation: " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+ c.JSON(http.StatusOK, responses.StandardResponse{
+ Status: http.StatusOK,
+ Message: "Postulation deleted successfully",
+ Data: nil,
+ })
+}
+
+func AdminDeleteUser(c *gin.Context) {
idUsuario := c.Query("usuario")
- err := configs.DB.Where("usuario = ?", idUsuario).Delete(&models.Usuario{}).Error
+ err := IsAdmin(c)
if err != nil {
c.JSON(http.StatusBadRequest, responses.StandardResponse{
- Status: 400,
- Message: "Error deleting user: " + err.Error(),
+ Status: http.StatusBadRequest,
+ Message: "Error getting privileges. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ roleToBeDeleted, err := RoleFromUser(models.Usuario{Usuario: idUsuario})
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Error getting role from the user to be deleted. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ fmt.Println(roleToBeDeleted)
+
+ if roleToBeDeleted == "admin" {
+ c.JSON(http.StatusForbidden, responses.StandardResponse{
+ Status: http.StatusForbidden,
+ Message: "Cannot delete an admin account",
+ Data: nil,
+ })
+ return
+ }
+
+ err = configs.DB.Where("usuario = ?", idUsuario).Delete(&models.Usuario{}).Error
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Error deleting user. " + err.Error(),
Data: nil,
})
return
}
c.JSON(http.StatusOK, responses.StandardResponse{
- Status: 200,
+ Status: http.StatusOK,
Message: "User deleted successfully",
Data: nil,
})
}
+
+type AdminDetailsStudent struct {
+ Correo string `json:"correo"`
+ Nombre string `json:"nombre"`
+ Apellido string `json:"apellido"`
+ Nacimiento time.Time `json:"nacimiento"`
+ Telefono string `json:"telefono"`
+ Carrera int `json:"carrera"`
+ Semestre int `json:"semestre"`
+ CV string `json:"cv"`
+ Foto string `json:"foto"`
+ Universidad string `json:"universidad"`
+ Suspendido bool `json:"suspendido"`
+}
+
+type AdminDetailsEnterprise struct {
+ Correo string `json:"correo"`
+ Nombre string `json:"nombre"`
+ Foto string `json:"foto"`
+ Detalles string `json:"detalles"`
+ Suspendido bool `json:"suspendido"`
+}
+
+func AdminGetUserDetails(c *gin.Context) {
+ var input UserDetailsInput // Correo del usuario a buscar
+
+ if err := c.ShouldBindJSON(&input); err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Invalid input. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ err := IsAdmin(c)
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Error getting privileges. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ userType, err := RoleFromUser(models.Usuario{Usuario: input.Correo})
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "User not found. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ switch userType {
+ case "student":
+ var usuario models.Usuario
+ var estudiante models.Estudiante
+
+ err = configs.DB.Where("usuario = ?", input.Correo).First(&usuario).Error
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "User not found. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ err = configs.DB.Where("id_estudiante = ?", input.Correo).First(&estudiante).Error
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Student not found. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ c.JSON(http.StatusOK, responses.StandardResponse{
+ Status: http.StatusOK,
+ Message: "Student found",
+ Data: map[string]interface{}{
+ "student": AdminDetailsStudent{
+ Correo: estudiante.Correo,
+ Nombre: estudiante.Nombre,
+ Apellido: estudiante.Apellido,
+ Nacimiento: estudiante.Nacimiento,
+ Telefono: estudiante.Telefono,
+ Carrera: estudiante.Carrera,
+ Semestre: estudiante.Semestre,
+ CV: estudiante.CV,
+ Foto: estudiante.Foto,
+ Universidad: estudiante.Universidad,
+ Suspendido: usuario.Suspendido,
+ },
+ },
+ })
+ case "enterprise":
+ var usuario models.Usuario
+ err = configs.DB.Where("usuario = ?", input.Correo).First(&usuario).Error
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "User not found. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ var empresa models.Empresa
+ err = configs.DB.Where("id_empresa = ?", input.Correo).First(&empresa).Error
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Enterprise not found. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ c.JSON(http.StatusOK, responses.StandardResponse{
+ Status: http.StatusOK,
+ Message: "Enterprise found",
+ Data: map[string]interface{}{
+ "company": AdminDetailsEnterprise{
+ Correo: empresa.Correo,
+ Nombre: empresa.Nombre,
+ Foto: empresa.Foto,
+ Detalles: empresa.Detalles,
+ Suspendido: usuario.Suspendido,
+ },
+ },
+ })
+ case "admin":
+ c.JSON(http.StatusForbidden, responses.StandardResponse{
+ Status: http.StatusForbidden,
+ Message: "Admins cannot be viewed",
+ Data: nil,
+ })
+ default:
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "User not found. " + err.Error(),
+ Data: nil,
+ })
+ }
+}
+
+type PostulationsResults struct {
+ IdUsuario string `json:"id_usuario"`
+ Nombre string `json:"nombre"`
+ Apellido string `json:"apellido"`
+ IdPostulacion int `json:"id_postulacion"`
+ IdOferta int `json:"id_oferta"`
+ Estado string `json:"estado"`
+}
+
+func GetPostulationsOfStudentAsAdmin(c *gin.Context) {
+ // Devolver lo mismo que GetPostulationsFromStudent pero como admin.
+
+ var results []PostulationsResults
+ var data map[string]interface{}
+
+ // id del estudiante como query.
+ idEstudiante := c.Query("id_estudiante")
+
+ err := IsAdmin(c)
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Error getting privileges. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ err = configs.DB.Raw("select u.usuario, e.nombre, e.apellido, p.id_postulacion, p.id_oferta, p.estado from usuario u join estudiante e on u.usuario = e.id_estudiante join postulacion p on e.id_estudiante = p.id_estudiante where u.usuario = ?", idEstudiante).Scan(&results).Error
+ fmt.Println(err)
+ fmt.Println(results)
+
+ if err != nil {
+ c.JSON(400, responses.StandardResponse{
+ Status: 400,
+ Message: "Error getting postulations",
+ Data: nil,
+ })
+ return
+ }
+
+ // meter los resultados en un mapa
+ data = map[string]interface{}{
+ "postulations": results,
+ }
+
+ c.JSON(200, responses.StandardResponse{
+ Status: 200,
+ Message: "Postulations retrieved successfully",
+ Data: data,
+ })
+}
diff --git a/backend/controllers/auth.go b/backend/controllers/auth.go
index b9ac42a4..17224921 100644
--- a/backend/controllers/auth.go
+++ b/backend/controllers/auth.go
@@ -8,38 +8,9 @@ import (
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
"net/http"
+ "time"
)
-type RegisterInput struct {
- Usuario string `json:"usuario"`
- Contra string `json:"contra"`
-}
-
-func Register(c *gin.Context) {
- var input RegisterInput
-
- // En esta línea se hace el binding del JSON que viene en el body del request a la variable input
- if err := c.ShouldBindJSON(&input); err != nil {
- c.JSON(400, responses.StandardResponse{Status: 400, Message: "Invalid input", Data: nil})
- return
- }
-
- u := models.Usuario{
- Usuario: input.Usuario,
- Contra: input.Contra,
- Suspendido: false,
- }
-
- _, err := u.SaveUser()
-
- if err != nil {
- c.JSON(400, responses.StandardResponse{Status: 400, Message: "Error creating user", Data: nil})
- return
- }
-
- c.JSON(200, responses.StandardResponse{Status: 200, Message: "Usuario created successfully", Data: nil})
-}
-
type LoginInput struct {
Usuario string `json:"usuario"`
Contra string `json:"contra"`
@@ -49,7 +20,11 @@ func Login(c *gin.Context) {
var input LoginInput
if err := c.ShouldBindJSON(&input); err != nil {
- c.JSON(400, responses.StandardResponse{Status: 400, Message: "Invalid input", Data: nil})
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Invalid input. " + err.Error(),
+ Data: nil,
+ })
return
}
@@ -62,8 +37,8 @@ func Login(c *gin.Context) {
if err != nil {
c.JSON(http.StatusUnauthorized, responses.StandardResponse{
- Status: 401,
- Message: "Invalid credentials: " + err.Error(),
+ Status: http.StatusUnauthorized,
+ Message: "Invalid credentials",
Data: nil,
})
return
@@ -74,7 +49,7 @@ func Login(c *gin.Context) {
if err != nil {
c.JSON(http.StatusNotFound, responses.StandardResponse{
Status: 404,
- Message: "Invalid credentials",
+ Message: "Could not verify role: " + err.Error(),
Data: nil,
})
return
@@ -180,19 +155,13 @@ func RoleFromUser(usuario models.Usuario) (string, error) {
}
func RoleFromToken(c *gin.Context) (string, error) {
- username, err := utils.ExtractTokenUsername(c)
+ username, err := utils.TokenExtractUsername(c)
if err != nil {
return "", err
}
- u, err := models.GetUserByUsername(username)
-
- if err != nil {
- return "", err
- }
-
- role, err := RoleFromUser(u)
+ role, err := RoleFromUser(models.Usuario{Usuario: username})
if err != nil {
return "", err
@@ -201,8 +170,8 @@ func RoleFromToken(c *gin.Context) (string, error) {
return role, nil
}
-func CurrentUser(c *gin.Context) {
- username, err := utils.ExtractTokenUsername(c)
+func GetCurrentUserDetails(c *gin.Context) {
+ username, err := utils.TokenExtractUsername(c)
if err != nil {
c.JSON(http.StatusBadRequest, responses.StandardResponse{
@@ -286,6 +255,26 @@ func CurrentUser(c *gin.Context) {
)
}
+type PublicDetailsStudent struct {
+ Correo string `json:"correo"`
+ Nombre string `json:"nombre"`
+ Apellido string `json:"apellido"`
+ Nacimiento time.Time `json:"nacimiento"`
+ Telefono string `json:"telefono"`
+ Carrera int `json:"carrera"`
+ Semestre int `json:"semestre"`
+ CV string `json:"cv"`
+ Foto string `json:"foto"`
+ Universidad string `json:"universidad"`
+}
+
+type PublicDetailsEnterprise struct {
+ Correo string `json:"correo"`
+ Nombre string `json:"nombre"`
+ Foto string `json:"foto"`
+ Detalles string `json:"detalles"`
+}
+
type UserDetailsInput struct {
Correo string `json:"correo"`
}
@@ -294,63 +283,92 @@ func GetUserDetails(c *gin.Context) {
var input UserDetailsInput
if err := c.ShouldBindJSON(&input); err != nil {
- c.JSON(400, responses.StandardResponse{Status: 400, Message: "Invalid input", Data: nil})
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Invalid input. " + err.Error(),
+ Data: nil,
+ })
return
}
var estudiante models.Estudiante
var empresa models.Empresa
- var administrador models.Administrador
- err := configs.DB.Where("correo = ?", input.Correo).First(&estudiante).Error
- if err == nil {
- c.JSON(http.StatusOK, responses.StandardResponse{
- Status: 200,
- Message: "User found",
- Data: map[string]interface{}{
- "estudiante": estudiante,
- },
+ userType, err := RoleFromUser(models.Usuario{Usuario: input.Correo})
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "User not found. " + err.Error(),
+ Data: nil,
})
return
}
- err = configs.DB.Where("correo = ?", input.Correo).First(&empresa).Error
+ switch userType {
+ case "student":
+ err = configs.DB.Where("id_estudiante = ?", input.Correo).First(&estudiante).Error
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Student not found. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
- if err == nil {
c.JSON(http.StatusOK, responses.StandardResponse{
- Status: 200,
- Message: "User found",
+ Status: http.StatusOK,
+ Message: "Student found",
Data: map[string]interface{}{
- "empresa": empresa,
+ "student": PublicDetailsStudent{
+ Correo: estudiante.Correo,
+ Nombre: estudiante.Nombre,
+ Apellido: estudiante.Apellido,
+ Nacimiento: estudiante.Nacimiento,
+ Telefono: estudiante.Telefono,
+ Carrera: estudiante.Carrera,
+ Semestre: estudiante.Semestre,
+ CV: estudiante.CV,
+ Foto: estudiante.Foto,
+ Universidad: estudiante.Universidad,
+ },
},
})
- return
- }
+ case "enterprise":
+ err = configs.DB.Where("id_empresa = ?", input.Correo).First(&empresa).Error
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Enterprise not found. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
- err = configs.DB.Where("correo = ?", input.Correo).First(&administrador).Error
-
- if err == nil {
- c.JSON(http.StatusUnauthorized, responses.StandardResponse{
- Status: 401,
- Message: "Access denied",
+ c.JSON(http.StatusOK, responses.StandardResponse{
+ Status: http.StatusOK,
+ Message: "Enterprise found",
+ Data: map[string]interface{}{
+ "company": PublicDetailsEnterprise{
+ Correo: empresa.Correo,
+ Nombre: empresa.Nombre,
+ Foto: empresa.Foto,
+ Detalles: empresa.Detalles,
+ },
+ },
+ })
+ case "admin":
+ c.JSON(http.StatusForbidden, responses.StandardResponse{
+ Status: http.StatusForbidden,
+ Message: "Admins cannot be viewed",
Data: nil,
})
- return
- }
-
- if err != nil {
+ default:
c.JSON(http.StatusBadRequest, responses.StandardResponse{
- Status: 400,
+ Status: http.StatusBadRequest,
Message: "User not found. " + err.Error(),
Data: nil,
})
- return
}
-
- c.JSON(http.StatusOK, responses.StandardResponse{
- Status: 400,
- Message: "User not found",
- Data: nil,
- },
- )
}
diff --git a/backend/controllers/carrer.go b/backend/controllers/carrer.go
index 190e272f..f611ffd0 100644
--- a/backend/controllers/carrer.go
+++ b/backend/controllers/carrer.go
@@ -5,6 +5,7 @@ import (
"backend/models"
"backend/responses"
"github.com/gin-gonic/gin"
+ "net/http"
)
type CareerInput struct {
@@ -15,10 +16,21 @@ type CareerInput struct {
func NewCareer(c *gin.Context) {
var input CareerInput
+ err := IsAdmin(c)
+
+ if err != nil {
+ c.JSON(http.StatusUnauthorized, responses.StandardResponse{
+ Status: http.StatusUnauthorized,
+ Message: "Error getting privileges: " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
if err := c.ShouldBindJSON(&input); err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
- Message: "Error binding JSON: " + err.Error(),
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Invalid input" + err.Error(),
Data: nil,
})
return
@@ -29,23 +41,22 @@ func NewCareer(c *gin.Context) {
Descripcion: input.Descripcion,
}
- err := configs.DB.Create(&carrera).Error
+ err = configs.DB.Create(&carrera).Error
if err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
- Message: "Error creating career. " + err.Error(),
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Error creating career" + err.Error(),
Data: nil,
})
return
}
- c.JSON(200, responses.StandardResponse{
- Status: 200,
- Message: "Carrera created successfully",
+ c.JSON(http.StatusOK, responses.StandardResponse{
+ Status: http.StatusOK,
+ Message: "Career created successfully",
Data: nil,
})
-
}
func GetCareers(c *gin.Context) {
@@ -54,9 +65,9 @@ func GetCareers(c *gin.Context) {
err := configs.DB.Find(&careers).Error
if err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
- Message: "Error getting careers",
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Error retrieving careers" + err.Error(),
Data: nil,
})
return
@@ -68,8 +79,8 @@ func GetCareers(c *gin.Context) {
"careers": careers,
}
- c.JSON(200, responses.StandardResponse{
- Status: 200,
+ c.JSON(http.StatusOK, responses.StandardResponse{
+ Status: http.StatusOK,
Message: "Careers retrieved successfully",
Data: data,
})
diff --git a/backend/controllers/chat.go b/backend/controllers/chat.go
index 2a8b1c82..c8f68fc3 100644
--- a/backend/controllers/chat.go
+++ b/backend/controllers/chat.go
@@ -80,7 +80,8 @@ func GetMessages(c *gin.Context) {
join estudiante es on m.id_emisor = es.id_estudiante or m.id_receptor = es.id_estudiante
join empresa em on m.id_emisor = em.id_empresa or m.id_receptor = em.id_empresa
where (id_emisor = ? and id_receptor = ?)
- or (id_emisor = ? and id_receptor = ?);`
+ or (id_emisor = ? and id_receptor = ?)
+ order by m.tiempo asc;`
// Ejecutamos la consulta SQL pura con parámetros inputID.ID_usuario
err := configs.DB.Raw(query, inputID.ID_emisor, inputID.ID_emisor, inputID.ID_receptor, inputID.ID_receptor,
@@ -179,3 +180,39 @@ func GetLastChat(c *gin.Context) {
Data: messageMap,
})
}
+
+type DeleteChatInput struct {
+ Id_Postulacion string `json:"id_postulacion"`
+}
+
+func DeleteChat(c *gin.Context) {
+
+ // Obtener el id_postulacion desde los query parameters
+ idPostulacion := c.Query("id_postulacion")
+
+ // Verifica si el valor del parámetro está presente
+ if idPostulacion == "" {
+ c.JSON(400, responses.StandardResponse{
+ Status: 400,
+ Message: "Missing id_postulacion parameter",
+ Data: nil,
+ })
+ return
+ }
+
+ err := configs.DB.Where("id_postulacion = ?", idPostulacion).Delete(&models.Mensaje{}).Error
+ if err != nil {
+ c.JSON(400, responses.StandardResponse{
+ Status: 400,
+ Message: "Error deleting chat",
+ Data: nil,
+ })
+ return
+ }
+
+ c.JSON(200, responses.StandardResponse{
+ Status: 200,
+ Message: "Chat deleted successfully",
+ Data: nil,
+ })
+}
diff --git a/backend/controllers/company.go b/backend/controllers/company.go
index 9963a506..1eec9197 100644
--- a/backend/controllers/company.go
+++ b/backend/controllers/company.go
@@ -4,6 +4,7 @@ import (
"backend/configs"
"backend/models"
"backend/responses"
+ "backend/utils"
"github.com/gin-gonic/gin"
"github.com/lib/pq"
"net/http"
@@ -12,7 +13,6 @@ import (
type EmpresaInput struct {
Nombre string `json:"nombre"`
Detalles string `json:"detalles"`
- Foto string `json:"foto"`
Correo string `json:"correo"`
Telefono string `json:"telefono"`
Contra string `json:"contra"`
@@ -23,8 +23,8 @@ func NewCompany(c *gin.Context) {
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, responses.StandardResponse{
- Status: 400,
- Message: "Error binding JSON: " + err.Error(),
+ Status: http.StatusBadRequest,
+ Message: "Invalid input. " + err.Error(),
Data: nil,
})
return
@@ -33,7 +33,6 @@ func NewCompany(c *gin.Context) {
e := models.Empresa{
IdEmpresa: input.Correo,
Nombre: input.Nombre,
- Foto: input.Foto,
Detalles: input.Detalles,
Correo: input.Correo,
Telefono: input.Telefono,
@@ -49,7 +48,7 @@ func NewCompany(c *gin.Context) {
if err != nil {
if pqErr, ok := err.(*pq.Error); ok && pqErr.Code == "23505" {
c.JSON(http.StatusConflict, responses.StandardResponse{
- Status: 409,
+ Status: http.StatusConflict,
Message: "User with this email already exists",
Data: nil,
})
@@ -57,7 +56,7 @@ func NewCompany(c *gin.Context) {
}
c.JSON(http.StatusBadRequest, responses.StandardResponse{
- Status: 400,
+ Status: http.StatusBadRequest,
Message: "Error creating user. " + err.Error(),
Data: nil,
})
@@ -68,7 +67,7 @@ func NewCompany(c *gin.Context) {
if err != nil {
c.JSON(http.StatusBadRequest, responses.StandardResponse{
- Status: 400,
+ Status: http.StatusBadRequest,
Message: "Error creating company. " + err.Error(),
Data: nil,
})
@@ -76,7 +75,7 @@ func NewCompany(c *gin.Context) {
}
c.JSON(http.StatusOK, responses.StandardResponse{
- Status: 200,
+ Status: http.StatusOK,
Message: "Company created successfully",
Data: nil,
})
@@ -86,35 +85,54 @@ func UpdateCompanies(c *gin.Context) {
var input EmpresaInput
if err := c.ShouldBindJSON(&input); err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
- Message: "Error binding JSON: " + err.Error(),
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Invalid input. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ user, err := utils.TokenExtractUsername(c)
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Could not retrieve info from token. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ // Se verifica que el usuario sea el mismo que el de la empresa
+ if user != input.Correo {
+ c.JSON(http.StatusUnauthorized, responses.StandardResponse{
+ Status: http.StatusUnauthorized,
+ Message: "User " + user + " is not authorized to update company " + input.Correo,
Data: nil,
})
return
}
// No se puede actualizar el correo/id de la empresa
- err := configs.DB.Model(&models.Empresa{}).Where("id_empresa = ?", input.Correo).Updates(models.Empresa{
+ err = configs.DB.Model(&models.Empresa{}).Where("id_empresa = ?", input.Correo).Updates(models.Empresa{
Nombre: input.Nombre,
Detalles: input.Detalles,
- Foto: input.Foto,
Telefono: input.Telefono,
}).Error
if err != nil {
c.JSON(http.StatusBadRequest, responses.StandardResponse{
- Status: 400,
- Message: "Error updating",
+ Status: http.StatusBadRequest,
+ Message: "Error updating company. " + err.Error(),
Data: nil,
})
return
}
c.JSON(http.StatusOK, responses.StandardResponse{
- Status: 200,
+ Status: http.StatusOK,
Message: "Company updated successfully",
Data: nil,
})
-
}
diff --git a/backend/controllers/files.go b/backend/controllers/files.go
index 9f7f3cfd..ae383502 100644
--- a/backend/controllers/files.go
+++ b/backend/controllers/files.go
@@ -17,7 +17,7 @@ import (
func UpdateProfilePicture() gin.HandlerFunc {
return func(c *gin.Context) {
- user, err := utils.ExtractTokenUsername(c)
+ user, err := utils.TokenExtractUsername(c)
acceptedFileTypes := []string{"png", "jpg", "jpeg"}
if err != nil {
@@ -34,10 +34,10 @@ func UpdateProfilePicture() gin.HandlerFunc {
fmt.Println("Username upload: " + user_stripped)
// single file
- file, _ := c.FormFile("file")
+ fileHeader, _ := c.FormFile("file")
// get the file type from filename
- fileType := file.Filename[strings.LastIndex(file.Filename, ".")+1:]
+ fileType := fileHeader.Filename[strings.LastIndex(fileHeader.Filename, ".")+1:]
if !utils.Contains(acceptedFileTypes, fileType) {
c.JSON(http.StatusBadRequest, responses.StandardResponse{
@@ -51,10 +51,9 @@ func UpdateProfilePicture() gin.HandlerFunc {
randomNumber := rand.Intn(9999999999-1111111111) + 1111111111
newFileName := user_stripped + "_" + fmt.Sprint(randomNumber) + "." + fileType
- file.Filename = newFileName
+ fileHeader.Filename = newFileName
dst := "./uploads/" + newFileName
- fmt.Println("File: " + dst)
// Eliminar archivos antiguos con el mismo prefijo de usuario
if err := deleteFilesWithPrefix("./uploads/", user_stripped); err != nil {
@@ -67,7 +66,7 @@ func UpdateProfilePicture() gin.HandlerFunc {
}
// Actualizar en base de datos
- userType, err := utils.ExtractTokenUserType(c)
+ userType, err := utils.TokenExtractRole(c)
if err != nil {
c.JSON(http.StatusUnauthorized, responses.StandardResponse{
@@ -85,7 +84,7 @@ func UpdateProfilePicture() gin.HandlerFunc {
}
// Save locally
- err = c.SaveUploadedFile(file, dst)
+ err = c.SaveUploadedFile(fileHeader, dst)
if err != nil {
c.JSON(http.StatusInternalServerError, responses.StandardResponse{
Status: http.StatusInternalServerError,
@@ -97,7 +96,126 @@ func UpdateProfilePicture() gin.HandlerFunc {
// send the file via HTTP to the file server
url := "http://ec2-13-57-42-212.us-west-1.compute.amazonaws.com/upload/"
- bearerToken := "Bearer " + utils.ExtractToken(c)
+ bearerToken := "Bearer " + utils.ExtractTokenFromRequest(c)
+
+ if err := utils.UploadFileToServer(url, bearerToken, fileHeader, dst); err != nil {
+ c.JSON(http.StatusInternalServerError, responses.StandardResponse{
+ Status: http.StatusInternalServerError,
+ Message: "Failed to upload file to server: " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ // Eliminar el archivo local. Ya no es necesario, ya que se subió al servidor de archivos
+ //fmt.Println("Deleting file: " + dst)
+ if err := os.Remove(dst); err != nil {
+ c.JSON(http.StatusInternalServerError, responses.StandardResponse{
+ Status: http.StatusInternalServerError,
+ Message: "Failed to delete local file: " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ c.JSON(http.StatusOK, responses.StandardResponse{
+ Status: http.StatusOK,
+ Message: "File uploaded successfully",
+ Data: map[string]interface{}{
+ "filename": newFileName,
+ },
+ })
+ }
+}
+
+func UpdateCV() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ user, err := utils.TokenExtractUsername(c)
+ acceptedFileTypes := []string{"pdf"}
+
+ if err != nil {
+ c.JSON(http.StatusUnauthorized, responses.StandardResponse{
+ Status: http.StatusUnauthorized,
+ Message: "Unauthorized. Cannot get information from token. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ // strip username from email. ignoring everything after @
+ user_stripped := user[:strings.Index(user, "@")]
+ fmt.Println("Username upload: " + user_stripped)
+
+ // single file
+ file, _ := c.FormFile("file")
+
+ // get the file type from filename
+ fileType := file.Filename[strings.LastIndex(file.Filename, ".")+1:]
+
+ if !utils.Contains(acceptedFileTypes, fileType) {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Invalid file type. Accepted file types are " + strings.Join(acceptedFileTypes, ", "),
+ Data: nil,
+ })
+ return
+ }
+
+ randomNumber := rand.Intn(9999999999-1111111111) + 1111111111
+ newFileName := user_stripped + "_" + fmt.Sprint(randomNumber) + "." + fileType
+
+ file.Filename = newFileName
+
+ dst := "./uploads/pdf/" + newFileName
+ fmt.Println("File: " + dst)
+
+ // Eliminar archivos antiguos con el mismo prefijo de usuario
+ if err := deleteFilesWithPrefix("./uploads/", user_stripped); err != nil {
+ c.JSON(http.StatusInternalServerError, responses.StandardResponse{
+ Status: http.StatusInternalServerError,
+ Message: "Failed to delete old files: " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ // Actualizar en base de datos
+ userType, err := utils.TokenExtractRole(c)
+
+ if err != nil {
+ c.JSON(http.StatusUnauthorized, responses.StandardResponse{
+ Status: http.StatusUnauthorized,
+ Message: "Unauthorized. Cannot get information from token. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ if userType != "student" {
+ c.JSON(http.StatusUnauthorized, responses.StandardResponse{
+ Status: http.StatusUnauthorized,
+ Message: "Unauthorized. Only students can upload CVs.",
+ Data: nil,
+ })
+ return
+ }
+
+ err = configs.DB.Model(&models.Estudiante{}).Where("correo = ?", user).Updates(models.Estudiante{CV: newFileName}).Error
+
+ // Save locally
+ err = c.SaveUploadedFile(file, dst)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, responses.StandardResponse{
+ Status: http.StatusInternalServerError,
+ Message: "Failed to save file: " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ // send the file via HTTP to the file server
+ url := "http://ec2-13-57-42-212.us-west-1.compute.amazonaws.com/upload/pdf/"
+ bearerToken := "Bearer " + utils.ExtractTokenFromRequest(c)
if err := utils.UploadFileToServer(url, bearerToken, file, dst); err != nil {
c.JSON(http.StatusInternalServerError, responses.StandardResponse{
@@ -108,6 +226,16 @@ func UpdateProfilePicture() gin.HandlerFunc {
return
}
+ // Eliminar el archivo local. Ya no es necesario, ya que se subió al servidor de archivos
+ if err := os.Remove(dst); err != nil {
+ c.JSON(http.StatusInternalServerError, responses.StandardResponse{
+ Status: http.StatusInternalServerError,
+ Message: "Failed to delete local file: " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
c.JSON(http.StatusOK, responses.StandardResponse{
Status: http.StatusOK,
Message: "File uploaded successfully",
@@ -125,7 +253,7 @@ func deleteFilesWithPrefix(directory, prefix string) error {
}
for _, file := range files {
- fmt.Println(file.Name())
+ //fmt.Println(file.Name())
if strings.HasPrefix(file.Name(), prefix) {
filePath := filepath.Join(directory, file.Name())
fmt.Println("Deleting file: " + filePath)
@@ -138,7 +266,7 @@ func deleteFilesWithPrefix(directory, prefix string) error {
return nil
}
-func GetFile() gin.HandlerFunc {
+func GetProfilePicture() gin.HandlerFunc {
return func(c *gin.Context) {
filename := c.Param("filename")
fileURL := configs.FileServer + filename
@@ -192,3 +320,58 @@ func GetFile() gin.HandlerFunc {
}
}
}
+
+func GetCV() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ filename := c.Param("filename")
+ fileURL := configs.FileServer + "pdf/" + filename
+
+ // Realizar una solicitud GET al servidor de archivos externo
+ resp, err := http.Get(fileURL)
+ if err != nil {
+ c.JSON(http.StatusNotFound, responses.StandardResponse{
+ Status: http.StatusNotFound,
+ Message: "Error al obtener el archivo: " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ defer func(Body io.ReadCloser) {
+ err := Body.Close()
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, responses.StandardResponse{
+ Status: http.StatusInternalServerError,
+ Message: "Error al cerrar el cuerpo de la respuesta del servidor de archivos externo: " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+ }(resp.Body)
+
+ if resp.StatusCode != http.StatusOK {
+ c.JSON(http.StatusNotFound, responses.StandardResponse{
+ Status: http.StatusNotFound,
+ Message: "Archivo no encontrado en el servidor de archivos externo",
+ Data: nil,
+ })
+ return
+ }
+
+ // Configurar las cabeceras de la respuesta para el cliente
+ c.Header("Content-Type", resp.Header.Get("Content-Type"))
+ c.Header("Content-Disposition", "inline; filename="+filename)
+ c.Header("Content-Length", resp.Header.Get("Content-Length"))
+
+ // Copiar el cuerpo de la respuesta del servidor de archivos al cuerpo de la respuesta de Gin
+ _, err = io.Copy(c.Writer, resp.Body)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, responses.StandardResponse{
+ Status: http.StatusInternalServerError,
+ Message: "Error al copiar el cuerpo de la respuesta del servidor de archivos externo: " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+ }
+}
diff --git a/backend/controllers/offer.go b/backend/controllers/offer.go
index fc8ed92a..0c0c2405 100644
--- a/backend/controllers/offer.go
+++ b/backend/controllers/offer.go
@@ -6,27 +6,34 @@ import (
"backend/responses"
"backend/utils"
"database/sql"
- "fmt"
"github.com/gin-gonic/gin"
"net/http"
+ "strconv"
+ "time"
)
type OfferInput struct {
- IDEmpresa string `json:"id_empresa"`
- Puesto string `json:"puesto"`
- Descripcion string `json:"descripcion"`
- Requisitos string `json:"requisitos"`
- Salario float64 `json:"salario"`
- IdCarreras []string `json:"id_carreras"`
+ IDEmpresa string `json:"id_empresa"`
+ Puesto string `json:"puesto"`
+ Descripcion string `json:"descripcion"`
+ Requisitos string `json:"requisitos"`
+ Salario float64 `json:"salario"`
+ IdCarreras []string `json:"id_carreras"`
+ Jornada string `json:"jornada"`
+ HoraInicio time.Time `json:"hora_inicio"`
+ HoraFin time.Time `json:"hora_fin"`
}
type AfterInsert struct {
- IdOferta int `json:"id_oferta"`
- IDEmpresa string `json:"id_empresa"`
- Puesto string `json:"puesto"`
- Descripcion string `json:"descripcion"`
- Requisitos string `json:"requisitos"`
- Salario float64 `json:"salario"`
+ IdOferta int `json:"id_oferta"`
+ IDEmpresa string `json:"id_empresa"`
+ Puesto string `json:"puesto"`
+ Descripcion string `json:"descripcion"`
+ Requisitos string `json:"requisitos"`
+ Salario float64 `json:"salario"`
+ Jornada string `json:"jornada"`
+ HoraInicio time.Time `json:"hora_inicio"`
+ HoraFin time.Time `json:"hora_fin"`
}
type AfterInsert2 struct {
@@ -38,45 +45,64 @@ func NewOffer(c *gin.Context) {
var input OfferInput
if err := c.BindJSON(&input); err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
Message: "Invalid input: " + err.Error(),
Data: nil,
})
return
}
+ user, err := utils.TokenExtractUsername(c)
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Error extracting information from token: " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ if user != input.IDEmpresa {
+ c.JSON(http.StatusForbidden, responses.StandardResponse{
+ Status: http.StatusForbidden,
+ Message: "The user in the token does not match the one in the request body",
+ Data: nil,
+ })
+ return
+ }
+
offer := models.Oferta{
IDEmpresa: input.IDEmpresa,
Puesto: input.Puesto,
Descripcion: input.Descripcion,
Requisitos: input.Requisitos,
Salario: input.Salario,
+ Jornada: input.Jornada,
+ HoraInicio: input.HoraInicio,
+ HoraFin: input.HoraFin,
}
- fmt.Println(offer)
-
var inserted AfterInsert
- err := configs.DB.Raw("INSERT INTO oferta (id_empresa, puesto, descripcion, requisitos, salario) VALUES (?, ?, ?, ?, ?) RETURNING id_oferta, id_empresa, puesto, descripcion, requisitos, salario", offer.IDEmpresa, offer.Puesto, offer.Descripcion, offer.Requisitos, offer.Salario).Scan(&inserted).Error
+ err = configs.DB.Raw("INSERT INTO oferta (id_empresa, puesto, descripcion, requisitos, salario, jornada, hora_inicio, hora_fin) VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING id_oferta, id_empresa, puesto, descripcion, requisitos, salario, jornada, hora_inicio, hora_fin", offer.IDEmpresa, offer.Puesto, offer.Descripcion, offer.Requisitos, offer.Salario, offer.Jornada, offer.HoraInicio, offer.HoraFin).Scan(&inserted).Error
if err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
Message: "Error creating offer: " + err.Error(),
Data: nil,
})
return
}
- fmt.Println("\ncarreras: ", input.IdCarreras)
-
// Insert into oferta_carrera table
for _, idCarrera := range input.IdCarreras {
var inserted2 AfterInsert2
err = configs.DB.Raw("INSERT INTO oferta_carrera (id_oferta, id_carrera) VALUES (?, ?) RETURNING id_oferta, id_carrera", inserted.IdOferta, idCarrera).Scan(&inserted2).Error
if err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
Message: "Error creating oferta_carrera: " + err.Error(),
Data: nil,
})
@@ -84,50 +110,96 @@ func NewOffer(c *gin.Context) {
}
}
- c.JSON(200, responses.StandardResponse{
- Status: 200,
- Message: "Offer and oferta_carrera created successfully",
+ c.JSON(http.StatusOK, responses.StandardResponse{
+ Status: http.StatusOK,
+ Message: "Offer created successfully",
Data: nil,
})
}
type OfferUpdateInput struct {
- Id_Oferta int `json:"id_oferta"`
- IDEmpresa string `json:"id_empresa"`
- Puesto string `json:"puesto"`
- Descripcion string `json:"descripcion"`
- Requisitos string `json:"requisitos"`
- Salario float64 `json:"salario"`
- IdCarreras []string `json:"id_carreras"`
+ Id_Oferta int `json:"id_oferta"`
+ IDEmpresa string `json:"id_empresa"`
+ Puesto string `json:"puesto"`
+ Descripcion string `json:"descripcion"`
+ Requisitos string `json:"requisitos"`
+ Salario float64 `json:"salario"`
+ IdCarreras []string `json:"id_carreras"`
+ Jornada string `json:"jornada"`
+ HoraInicio time.Time `json:"hora_inicio"`
+ HoraFin time.Time `json:"hora_fin"`
}
func UpdateOffer(c *gin.Context) {
var input OfferUpdateInput
- var offer models.Oferta
+ var updatedOffer models.Oferta
if err := c.BindJSON(&input); err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
Message: "Invalid input: " + err.Error(),
Data: nil,
})
return
}
- offer = models.Oferta{
- IDEmpresa: input.IDEmpresa,
+ user, err := utils.TokenExtractUsername(c)
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Error extracting information from token: " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ originalOffer := models.Oferta{}
+ err = configs.DB.Where("id_oferta = ? AND id_empresa = ?", input.Id_Oferta, input.IDEmpresa).First(&originalOffer).Error
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Error getting original offer: " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ if originalOffer.IDEmpresa != user {
+ c.JSON(http.StatusForbidden, responses.StandardResponse{
+ Status: http.StatusForbidden,
+ Message: "The user in the token does not match the owner of the offer",
+ Data: nil,
+ })
+ return
+ }
+
+ if user != input.IDEmpresa {
+ c.JSON(http.StatusForbidden, responses.StandardResponse{
+ Status: http.StatusForbidden,
+ Message: "The user in the token does not match the one in the request body",
+ Data: nil,
+ })
+ return
+ }
+
+ updatedOffer = models.Oferta{
Puesto: input.Puesto,
Descripcion: input.Descripcion,
Requisitos: input.Requisitos,
Salario: input.Salario,
+ Jornada: input.Jornada,
+ HoraInicio: input.HoraInicio,
+ HoraFin: input.HoraFin,
}
- err := configs.DB.Model(&offer).Where("id_oferta = ?", input.Id_Oferta).Updates(offer).Error
+ err = configs.DB.Model(&updatedOffer).Where("id_oferta = ? AND id_empresa = ?", input.Id_Oferta, input.IDEmpresa).Updates(updatedOffer).Error
if err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
Message: "Error updating offer: " + err.Error(),
Data: nil,
})
@@ -138,8 +210,8 @@ func UpdateOffer(c *gin.Context) {
err = configs.DB.Where("id_oferta = ?", input.Id_Oferta).Delete(&models.OfertaCarrera{}).Error
if err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
Message: "Error deleting oferta_carrera to update them: " + err.Error(),
Data: nil,
})
@@ -147,12 +219,28 @@ func UpdateOffer(c *gin.Context) {
}
// Insert into oferta_carrera table
- for _, idCarrera := range input.IdCarreras {
- var inserted2 AfterInsert2
- err = configs.DB.Raw("INSERT INTO oferta_carrera (id_oferta, id_carrera) VALUES (?, ?) RETURNING id_oferta, id_carrera", input.Id_Oferta, idCarrera).Scan(&inserted2).Error
+ for _, idCarreraStr := range input.IdCarreras {
+ // Convierte la cadena 'idCarreraStr' a un entero 'idCarrera'
+ idCarrera, err := strconv.Atoi(idCarreraStr)
if err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
+ // Manejar el error si la conversión falla
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Error converting 'idCarrera' to int: " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ ofertaCarrera := models.OfertaCarrera{
+ IdOferta: input.Id_Oferta,
+ IdCarrera: idCarrera,
+ }
+
+ // Insertar en la tabla oferta_carrera usando Gorm
+ if err := configs.DB.Create(&ofertaCarrera).Error; err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
Message: "Error creating oferta_carrera: " + err.Error(),
Data: nil,
})
@@ -160,8 +248,8 @@ func UpdateOffer(c *gin.Context) {
}
}
- c.JSON(200, responses.StandardResponse{
- Status: 200,
+ c.JSON(http.StatusOK, responses.StandardResponse{
+ Status: http.StatusOK,
Message: "Offer updated successfully",
Data: nil,
})
@@ -178,8 +266,8 @@ func GetOffer(c *gin.Context) {
var input OfferGet
if err := c.ShouldBindJSON(&input); err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
Message: "Invalid input: " + err.Error(),
Data: nil,
})
@@ -189,8 +277,8 @@ func GetOffer(c *gin.Context) {
err := configs.DB.Where("id_oferta = ?", input.Id_Oferta).First(&offer).Error
if err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
Message: "Error getting offer: " + err.Error(),
Data: nil,
})
@@ -200,8 +288,8 @@ func GetOffer(c *gin.Context) {
err = configs.DB.Where("id_empresa = ?", offer.IDEmpresa).First(&Company).Error
if err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
Message: "Error getting company: " + err.Error(),
Data: nil,
})
@@ -213,8 +301,8 @@ func GetOffer(c *gin.Context) {
"company": Company,
}
- c.JSON(200, responses.StandardResponse{
- Status: 200,
+ c.JSON(http.StatusOK, responses.StandardResponse{
+ Status: http.StatusOK,
Message: "Offer retrieved successfully",
Data: data,
})
@@ -225,13 +313,16 @@ type GetOfferByCompanyInput struct {
}
type GetOfferByCompanyResponse struct {
- Id_Oferta int `json:"id_oferta"`
- IDEmpresa string `json:"id_empresa"`
- Puesto string `json:"puesto"`
- Descripcion string `json:"descripcion"`
- Requisitos string `json:"requisitos"`
- Salario float64 `json:"salario"`
- IdCarreras []int `json:"id_carreras"`
+ Id_Oferta int `json:"id_oferta"`
+ IDEmpresa string `json:"id_empresa"`
+ Puesto string `json:"puesto"`
+ Descripcion string `json:"descripcion"`
+ Requisitos string `json:"requisitos"`
+ Salario float64 `json:"salario"`
+ IdCarreras []int `json:"id_carreras"`
+ Jornada string `json:"jornada"`
+ HoraInicio time.Time `json:"hora_inicio"`
+ HoraFin time.Time `json:"hora_fin"`
}
func GetOfferByCompany(c *gin.Context) {
@@ -240,8 +331,8 @@ func GetOfferByCompany(c *gin.Context) {
var input GetOfferByCompanyInput
if err := c.ShouldBindJSON(&input); err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
Message: "Invalid input: " + err.Error(),
Data: nil,
})
@@ -256,7 +347,10 @@ func GetOfferByCompany(c *gin.Context) {
o.descripcion,
o.requisitos,
o.salario,
- oc.id_carrera
+ oc.id_carrera,
+ o.jornada,
+ o.hora_inicio,
+ o.hora_fin
FROM
oferta o
LEFT JOIN
@@ -268,8 +362,8 @@ func GetOfferByCompany(c *gin.Context) {
rows, err := configs.DB.Raw(query, input.Id_Empresa).Rows()
if err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
Message: "Error getting offers: " + err.Error(),
Data: nil,
})
@@ -305,6 +399,9 @@ func GetOfferByCompany(c *gin.Context) {
&offer.Requisitos,
&offer.Salario,
&idCarrera,
+ &offer.Jornada,
+ &offer.HoraInicio,
+ &offer.HoraFin,
); err != nil {
c.JSON(400, responses.StandardResponse{
Status: 400,
@@ -342,8 +439,8 @@ func GetOfferByCompany(c *gin.Context) {
"offers": offersResponse,
}
- c.JSON(200, responses.StandardResponse{
- Status: 200,
+ c.JSON(http.StatusOK, responses.StandardResponse{
+ Status: http.StatusOK,
Message: "Offers retrieved successfully",
Data: data,
})
@@ -359,39 +456,59 @@ func DeleteOffer(c *gin.Context) {
// Verifica si el valor del parámetro está presente
if idOferta == "" {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
- Message: "Missing id_oferta parameter",
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Missing id_oferta query parameter",
+ Data: nil,
+ })
+ return
+ }
+
+ user, err := utils.TokenExtractUsername(c)
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Error extracting information from token: " + err.Error(),
Data: nil,
})
return
}
- // @mark, esto lo hace la base de datos con el ON DELETE CASCADE.
- // Delete oferta_carrera
- err := configs.DB.Where("id_oferta = ?", idOferta).Delete(&models.OfertaCarrera{}).Error
+ // Verifica si el usuario es el dueño de la oferta
+ var offer models.Oferta
+ err = configs.DB.Where("id_oferta = ?", idOferta).First(&offer).Error
+
if err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
- Message: "Error deleting oferta_carrera: " + err.Error(),
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Error getting offer: " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ if offer.IDEmpresa != user {
+ c.JSON(http.StatusForbidden, responses.StandardResponse{
+ Status: http.StatusForbidden,
+ Message: "The user in the token does not match the owner of the offer",
Data: nil,
})
return
}
- // Delete oferta
- err = configs.DB.Where("id_oferta = ?", idOferta).Delete(&models.Oferta{}).Error
+ err = configs.DB.Where("id_oferta = ? AND id_empresa = ?", idOferta, user).Delete(&models.Oferta{}).Error
if err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
Message: "Error deleting oferta: " + err.Error(),
Data: nil,
})
return
}
- c.JSON(200, responses.StandardResponse{
- Status: 200,
+ c.JSON(http.StatusOK, responses.StandardResponse{
+ Status: http.StatusOK,
Message: "Offer deleted successfully",
Data: nil,
})
@@ -402,15 +519,15 @@ func GetApplicants(c *gin.Context) {
var tokenUsername string
if err := c.ShouldBindJSON(&input); err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
- Message: "Error binding JSON. " + err.Error(),
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Invalid input. " + err.Error(),
Data: nil,
})
return
}
- tokenUsername, err := utils.ExtractTokenUsername(c)
+ tokenUsername, err := utils.TokenExtractUsername(c)
if err != nil {
c.JSON(http.StatusBadRequest, responses.StandardResponse{
@@ -423,12 +540,21 @@ func GetApplicants(c *gin.Context) {
// Verificación con el token para que no se pueda ver las postulaciones de otras empresas
var offer models.Oferta
- err = configs.DB.Where("id_oferta = ? AND id_empresa = ?", input.IdOferta, tokenUsername).First(&offer).Error
+ err = configs.DB.Where("id_oferta = ?", input.IdOferta).First(&offer).Error
if err != nil {
+ c.JSON(http.StatusNotFound, responses.StandardResponse{
+ Status: http.StatusNotFound,
+ Message: "Error getting offer. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ if offer.IDEmpresa != tokenUsername {
c.JSON(http.StatusForbidden, responses.StandardResponse{
Status: http.StatusForbidden,
- Message: "Error verifying ownership of the offer. " + err.Error(),
+ Message: "The user in the token does not match the owner of the offer",
Data: nil,
})
return
diff --git a/backend/controllers/postulation.go b/backend/controllers/postulation.go
index e55f3460..961b673a 100644
--- a/backend/controllers/postulation.go
+++ b/backend/controllers/postulation.go
@@ -5,10 +5,10 @@ import (
"backend/models"
"backend/responses"
"backend/utils"
+ "fmt"
"github.com/gin-gonic/gin"
"github.com/lib/pq"
"net/http"
- "strings"
"time"
)
@@ -22,6 +22,10 @@ type GetPostulationInput struct {
IdOferta int `json:"id_oferta"`
}
+type PuestoResult struct {
+ Puesto string
+}
+
func NewPostulation(c *gin.Context) {
var input PostulationInput
@@ -34,6 +38,26 @@ func NewPostulation(c *gin.Context) {
return
}
+ user, err := utils.TokenExtractUsername(c)
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Unauthorized. Cannot get information from token. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ if user != input.IdEstudiante {
+ c.JSON(http.StatusForbidden, responses.StandardResponse{
+ Status: http.StatusForbidden,
+ Message: "The user in the token does not match the user in the request.",
+ Data: nil,
+ })
+ return
+ }
+
postulation := models.Postulacion{
IdOferta: input.IdOferta,
IdEstudiante: input.IdEstudiante,
@@ -42,40 +66,57 @@ func NewPostulation(c *gin.Context) {
var inserted models.PostulacionGet
- // TODO: Delete Raw
- err := configs.DB.Raw("INSERT INTO postulacion (id_oferta, id_estudiante, estado) VALUES (?, ?, ?) RETURNING id_postulacion, id_oferta, id_estudiante, estado", postulation.IdOferta, postulation.IdEstudiante, postulation.Estado).Scan(&inserted).Error
+ err = configs.DB.Raw("INSERT INTO postulacion (id_oferta, id_estudiante, estado) VALUES (?, ?, ?) RETURNING id_postulacion, id_oferta, id_estudiante, estado", postulation.IdOferta, postulation.IdEstudiante, postulation.Estado).Scan(&inserted).Error
if err != nil {
if pqErr, ok := err.(*pq.Error); ok && pqErr.Code == "23505" {
c.JSON(http.StatusConflict, responses.StandardResponse{
- Status: 409,
+ Status: http.StatusConflict,
Message: "This postulation already exists",
Data: nil,
})
return
}
- c.JSON(400, responses.StandardResponse{
- Status: 400,
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
Message: "Error creating. " + err.Error(),
Data: nil,
})
return
}
+ var resultado PuestoResult
+
+ // Obtener el valor de "puesto" de la oferta
+ err = configs.DB.Model(models.Oferta{}).Select("puesto").Where("id_oferta = ?", input.IdOferta).Scan(&resultado).Error
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Error getting 'puesto' from oferta. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ puesto := resultado.Puesto
+
+ // Mensaje con el valor de "puesto"
+ mensaje := fmt.Sprintf("Hola, me acabo de postular al puesto de '%s'.", puesto)
+
// Nuevo query
- err = configs.DB.Exec("INSERT INTO mensaje (id_postulacion, id_emisor, id_receptor, mensaje, tiempo) VALUES (?, ?, (SELECT id_empresa FROM oferta WHERE id_oferta = ?), 'Hola, me acabo de postular a esta oferta.', ?)", inserted.IdPostulacion, inserted.IdEstudiante, inserted.IdOferta, time.Now()).Error
+ err = configs.DB.Exec("INSERT INTO mensaje (id_postulacion, id_emisor, id_receptor, mensaje, tiempo) VALUES (?, ?, (SELECT id_empresa FROM oferta WHERE id_oferta = ?), ?, ?)", inserted.IdPostulacion, inserted.IdEstudiante, input.IdOferta, mensaje, time.Now()).Error
if err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
Message: "Error creating initial message. " + err.Error(),
Data: nil,
})
return
}
- c.JSON(200, responses.StandardResponse{
- Status: 200,
+ c.JSON(http.StatusOK, responses.StandardResponse{
+ Status: http.StatusOK,
Message: "Postulation created successfully",
Data: nil,
})
@@ -100,76 +141,25 @@ type PostulationResult struct {
func GetOfferPreviews(c *gin.Context) {
var postulations []models.ViewPrevPostulaciones
- var data map[string]interface{}
err := configs.DB.Find(&postulations).Error
if err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
- Message: "Error getting postulations",
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Error getting postulations. " + err.Error(),
Data: nil,
})
return
}
- groupedPostulations := make(map[int][]string)
- for _, p := range postulations {
- groupedPostulations[p.IdOferta] = append(groupedPostulations[p.IdOferta], p.NombreCarrera)
- }
-
- combinedPostulations := make([]map[string]interface{}, 0)
- for id, carreras := range groupedPostulations {
- combinedCarreras := strings.Join(carreras, ", ")
-
- combinedPostulations = append(combinedPostulations, map[string]interface{}{
- "id_oferta": id,
- "puesto": getPuestosByIDOferta(postulations, id)[0],
- "nombre_empresa": getNombreEmpresaByIDOferta(postulations, id),
- "nombre_carreras": combinedCarreras,
- "salario": getSalarioByIDOferta(postulations, id),
- })
- }
-
- data = map[string]interface{}{
- "postulations": combinedPostulations,
- }
-
- c.JSON(200, responses.StandardResponse{
- Status: 200,
- Message: "Postulations retrieved successfully",
- Data: data,
+ c.JSON(http.StatusOK, responses.StandardResponse{
+ Status: http.StatusOK,
+ Message: "Previews of offers retrieved successfully",
+ Data: map[string]interface{}{"postulations": postulations},
})
}
-func getPuestosByIDOferta(postulations []models.ViewPrevPostulaciones, id int) []string {
- var puestos []string
- for _, p := range postulations {
- if p.IdOferta == id {
- puestos = append(puestos, p.Puesto)
- }
- }
- return puestos
-}
-
-func getNombreEmpresaByIDOferta(postulations []models.ViewPrevPostulaciones, id int) string {
- for _, p := range postulations {
- if p.IdOferta == id {
- return p.NombreEmpresa
- }
- }
- return ""
-}
-
-func getSalarioByIDOferta(postulations []models.ViewPrevPostulaciones, id int) float64 {
- for _, p := range postulations {
- if p.IdOferta == id {
- return p.Salario
- }
- }
- return 0
-}
-
type PostulationFromStudentResult struct {
IDPostulacion int `json:"id_postulacion"`
IDOferta int `json:"id_oferta"`
@@ -180,28 +170,21 @@ type PostulationFromStudentResult struct {
Salario float64 `json:"salario"`
}
-func GetPostulactionFromStudent(c *gin.Context) {
+func GetPostulationFromStudent(c *gin.Context) {
var results []PostulationFromStudentResult
var data map[string]interface{}
- // obten el id del estudiante a partir del token.
- idEstudiante, err := utils.ExtractTokenUsername(c)
+ idEstudiante, err := utils.TokenExtractUsername(c)
+
if err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
- Message: "Error getting id estudiante",
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Could not retrieve info from token. " + err.Error(),
Data: nil,
})
return
}
- // en postgreSQL:
- //select id_postulacion, o.id_oferta, id_empresa, puesto, descripcion, requisitos, salario
- //from postulacion p
- //join oferta o
- //on p.id_oferta = o.id_oferta
- //where id_estudiante = 'mor21146@uvg.edu.gt';
-
err = configs.DB.Raw("select id_postulacion, o.id_oferta, id_empresa, puesto, descripcion, requisitos, salario from postulacion p join oferta o on p.id_oferta = o.id_oferta where id_estudiante = ?", idEstudiante).Scan(&results).Error
if err != nil {
@@ -227,7 +210,7 @@ func GetPostulactionFromStudent(c *gin.Context) {
func RetirePostulation(c *gin.Context) {
input := c.Query("id_postulacion")
- user, err := utils.ExtractTokenUsername(c)
+ user, err := utils.TokenExtractUsername(c)
if input == "" {
c.JSON(http.StatusBadRequest, responses.StandardResponse{
@@ -250,7 +233,7 @@ func RetirePostulation(c *gin.Context) {
// verify that the postulation exists
var postulation models.Postulacion
- err = configs.DB.Where("id_postulacion = ? AND id_estudiante = ?", input, user).First(&postulation).Error
+ err = configs.DB.Where("id_postulacion = ?", input).First(&postulation).Error
if err != nil {
c.JSON(http.StatusNotFound, responses.StandardResponse{
@@ -261,6 +244,16 @@ func RetirePostulation(c *gin.Context) {
return
}
+ // verify that the postulation belongs to the user
+ if postulation.IdEstudiante != user {
+ c.JSON(http.StatusForbidden, responses.StandardResponse{
+ Status: http.StatusForbidden,
+ Message: "The postulation does not belong to the user in the token",
+ Data: nil,
+ })
+ return
+ }
+
err = configs.DB.Where("id_postulacion = ? AND id_estudiante = ?", input, user).Delete(&models.Postulacion{}).Error
if err != nil {
diff --git a/backend/controllers/students.go b/backend/controllers/students.go
index 0b283d08..2f2bd571 100644
--- a/backend/controllers/students.go
+++ b/backend/controllers/students.go
@@ -4,6 +4,7 @@ import (
"backend/configs"
"backend/models"
"backend/responses"
+ "backend/utils"
"github.com/gin-gonic/gin"
"github.com/lib/pq"
"net/http"
@@ -97,13 +98,58 @@ func NewStudent(c *gin.Context) {
})
}
+type EstudianteUpdateInput struct {
+ Nombre string `json:"nombre"`
+ Apellido string `json:"apellido"`
+ Nacimiento string `json:"nacimiento"`
+ Correo string `json:"correo"`
+ Telefono string `json:"telefono"`
+ Carrera int `json:"carrera"`
+ Semestre int `json:"semestre"`
+ Foto string `json:"foto"`
+ Universidad string `json:"universidad"`
+}
+
func UpdateStudent(c *gin.Context) {
- var input EstudianteInput
+ var input EstudianteUpdateInput
if err := c.ShouldBindJSON(&input); err != nil {
- c.JSON(400, responses.StandardResponse{
- Status: 400,
- Message: "Error binding JSON: " + err.Error(),
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Invalid input. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ var originalStudent models.Estudiante
+ err := configs.DB.Where("id_estudiante = ?", input.Correo).First(&originalStudent).Error
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Student '" + input.Correo + "' not found: " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ user, err := utils.TokenExtractUsername(c)
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, responses.StandardResponse{
+ Status: http.StatusBadRequest,
+ Message: "Could not retrieve info from token. " + err.Error(),
+ Data: nil,
+ })
+ return
+ }
+
+ // Se verifica que el usuario sea el mismo que el del estudiante
+ if user != input.Correo {
+ c.JSON(http.StatusUnauthorized, responses.StandardResponse{
+ Status: http.StatusUnauthorized,
+ Message: "User " + user + " is not authorized to update student " + input.Correo,
Data: nil,
})
return
@@ -111,23 +157,31 @@ func UpdateStudent(c *gin.Context) {
nacimiento, _ := time.Parse("2006-01-02", input.Nacimiento)
- var inserted models.EstudianteGet
+ // Crear una instancia del modelo Estudiante con los datos actualizados
+ updatedStudent := models.Estudiante{
+ Nombre: input.Nombre,
+ Apellido: input.Apellido,
+ Nacimiento: nacimiento,
+ Telefono: input.Telefono,
+ Carrera: input.Carrera,
+ Semestre: input.Semestre,
+ Universidad: input.Universidad,
+ }
- err := configs.DB.Raw("UPDATE estudiante SET nombre = ?, apellido = ?, nacimiento = ?, telefono = ?, carrera = ?, semestre = ?, cv = ?, foto = ?, universidad = ? WHERE id_estudiante = ? RETURNING id_estudiante", input.Nombre, input.Apellido, nacimiento, input.Telefono, input.Carrera, input.Semestre, input.CV, input.Foto, input.Universidad, input.Correo).Scan(&inserted).Error
+ err = configs.DB.Model(&models.Estudiante{}).Where("id_estudiante = ?", input.Correo).Updates(updatedStudent).Error
if err != nil {
c.JSON(http.StatusBadRequest, responses.StandardResponse{
- Status: 400,
- Message: "Error updating. " + err.Error(),
+ Status: http.StatusBadRequest,
+ Message: "Error updating student. " + err.Error(),
Data: nil,
})
return
}
c.JSON(http.StatusOK, responses.StandardResponse{
- Status: 200,
+ Status: http.StatusOK,
Message: "Student updated successfully",
Data: nil,
})
-
}
diff --git a/backend/main.go b/backend/main.go
index 942d547e..85822975 100644
--- a/backend/main.go
+++ b/backend/main.go
@@ -12,6 +12,10 @@ func main() {
panic(err)
}
+ if _, err := configs.CreateDirIfNotExist("./uploads/pdf"); err != nil {
+ panic(err)
+ }
+
router := gin.Default()
router.Use(CORS())
diff --git a/backend/middlewares/middlewares.go b/backend/middlewares/middlewares.go
index a4dd69af..810a1222 100644
--- a/backend/middlewares/middlewares.go
+++ b/backend/middlewares/middlewares.go
@@ -13,7 +13,7 @@ func JwtAuthentication() gin.HandlerFunc {
if err != nil {
c.JSON(http.StatusUnauthorized, responses.StandardResponse{
Status: 401,
- Message: "Unauthorized: " + err.Error(),
+ Message: "Unauthorized, token is invalid: " + err.Error(),
Data: nil,
})
diff --git a/backend/models/administrador.go b/backend/models/administrador.go
index b83afa69..c04d4329 100644
--- a/backend/models/administrador.go
+++ b/backend/models/administrador.go
@@ -1,9 +1,9 @@
package models
type Administrador struct {
- IdAdministrador string `json:"id_administrador"`
- Nombre string `json:"nombre"`
- Apellido string `json:"apellido"`
+ IdAdmin string `json:"id_admin"`
+ Nombre string `json:"nombre"`
+ Apellido string `json:"apellido"`
}
func (Administrador) TableName() string {
diff --git a/backend/models/oferta.go b/backend/models/oferta.go
index 902dd289..fd0d762c 100644
--- a/backend/models/oferta.go
+++ b/backend/models/oferta.go
@@ -1,11 +1,16 @@
package models
+import "time"
+
type Oferta struct {
- IDEmpresa string `json:"id_empresa"`
- Puesto string `json:"puesto"`
- Descripcion string `json:"descripcion"`
- Requisitos string `json:"requisitos"`
- Salario float64 `json:"salario"`
+ IDEmpresa string `json:"id_empresa"`
+ Puesto string `json:"puesto"`
+ Descripcion string `json:"descripcion"`
+ Requisitos string `json:"requisitos"`
+ Salario float64 `json:"salario"`
+ Jornada string `json:"jornada"`
+ HoraInicio time.Time `json:"hora_inicio"`
+ HoraFin time.Time `json:"hora_fin"`
}
// TableName Esta función se llama automáticamente cuando se hace un Create() en el ORM, acá va el nombre como aparece en Postgres
@@ -14,12 +19,15 @@ func (Oferta) TableName() string {
}
type OfertaGet struct {
- Id_Oferta int `json:"id_oferta"`
- IDEmpresa string `json:"id_empresa"`
- Puesto string `json:"puesto"`
- Descripcion string `json:"descripcion"`
- Requisitos string `json:"requisitos"`
- Salario float64 `json:"salario"`
+ Id_Oferta int `json:"id_oferta"`
+ IDEmpresa string `json:"id_empresa"`
+ Puesto string `json:"puesto"`
+ Descripcion string `json:"descripcion"`
+ Requisitos string `json:"requisitos"`
+ Salario float64 `json:"salario"`
+ Jornada string `json:"jornada"`
+ HoraInicio time.Time `json:"hora_inicio"`
+ HoraFin time.Time `json:"hora_fin"`
}
// TableName Esta función se llama automáticamente cuando se hace un Create() en el ORM, acá va el nombre como aparece en Postgres
diff --git a/backend/models/viewPrevPostulaciones.go b/backend/models/viewPrevPostulaciones.go
index a8f94ac4..03cb3008 100644
--- a/backend/models/viewPrevPostulaciones.go
+++ b/backend/models/viewPrevPostulaciones.go
@@ -1,11 +1,16 @@
package models
+import "time"
+
type ViewPrevPostulaciones struct {
- IdOferta int `json:"id_oferta"`
- Puesto string `json:"puesto"`
- NombreEmpresa string `json:"nombre_empresa"`
- NombreCarrera string `json:"nombre_carrera"`
- Salario float64 `json:"salario"`
+ IdOferta int `json:"id_oferta"`
+ Puesto string `json:"puesto"`
+ NombreEmpresa string `json:"nombre_empresa"`
+ NombreCarrera string `json:"nombre_carrera"`
+ Salario float64 `json:"salario"`
+ Jornada string `json:"jornada"`
+ HoraInicio time.Time `json:"hora_inicio"`
+ HoraFin time.Time `json:"hora_fin"`
}
func (ViewPrevPostulaciones) TableName() string {
diff --git a/backend/routes/routes_handler.go b/backend/routes/routes_handler.go
index 4c10d9c8..8cd22b77 100644
--- a/backend/routes/routes_handler.go
+++ b/backend/routes/routes_handler.go
@@ -10,14 +10,14 @@ func Routes(router *gin.Engine) {
// Rutas públicas
public := router.Group("/api")
- public.POST("/register", controllers.Register)
public.POST("/login", controllers.Login)
public.POST("/students", controllers.NewStudent)
public.POST("/companies", controllers.NewCompany)
public.GET("/postulations/previews", controllers.GetOfferPreviews)
// Rutas de archivos
- public.GET("/uploads/:filename", controllers.GetFile())
+ public.GET("/uploads/:filename", controllers.GetProfilePicture())
+ public.GET("cv/:filename", controllers.GetCV())
// Rutas protegidas
// Mensajes
@@ -27,12 +27,13 @@ func Routes(router *gin.Engine) {
messages.POST("/send", controllers.SendMessage)
messages.POST("/get", controllers.GetMessages)
messages.POST("/getLast", controllers.GetLastChat)
+ messages.DELETE("/delete", controllers.DeleteChat)
// Usuarios
users := router.Group("api/users")
users.Use(middlewares.JwtAuthentication())
- users.GET("/", controllers.CurrentUser)
+ users.GET("/", controllers.GetCurrentUserDetails)
users.POST("/details", controllers.GetUserDetails)
users.PUT("/upload", controllers.UpdateProfilePicture())
@@ -41,6 +42,7 @@ func Routes(router *gin.Engine) {
students.Use(middlewares.JwtAuthentication())
students.PUT("/update", controllers.UpdateStudent)
+ students.PUT("/update/cv", controllers.UpdateCV())
// Carreras
careers := router.Group("api/careers")
@@ -52,19 +54,20 @@ func Routes(router *gin.Engine) {
// Empresas
companies := router.Group("api/companies")
companies.Use(middlewares.JwtAuthentication())
-
companies.PUT("/update", controllers.UpdateCompanies)
// Administradores
admins := router.Group("api/admins")
admins.Use(middlewares.JwtAuthentication())
- admins.POST("/", controllers.NewAdmin)
- admins.GET("/students", controllers.GetStudents)
- admins.GET("/companies", controllers.GetCompanies)
- admins.POST("/suspend", controllers.SuspendAccount)
- admins.POST("/offers", controllers.DeleteOfferAdmin)
- admins.POST("/deleteUser", controllers.DeleteUsuario)
+ admins.GET("/students", controllers.AdminGetStudents)
+ admins.GET("/companies", controllers.AdminGetCompanies)
+ admins.POST("/suspend", controllers.AdminSuspendAccount)
+ admins.DELETE("/delete/offers", controllers.AdminDeleteOffer)
+ admins.POST("/delete/user", controllers.AdminDeleteUser)
+ admins.DELETE("/postulation", controllers.AdminDeletePostulation)
+ admins.POST("/details", controllers.AdminGetUserDetails)
+ admins.POST("/postulations", controllers.GetPostulationsOfStudentAsAdmin)
// Ofertas
offers := router.Group("api/offers")
@@ -81,6 +84,6 @@ func Routes(router *gin.Engine) {
postulations := router.Group("api/postulations")
postulations.Use(middlewares.JwtAuthentication())
postulations.POST("/", controllers.NewPostulation)
- postulations.GET("/getFromStudent", controllers.GetPostulactionFromStudent)
+ postulations.GET("/getFromStudent", controllers.GetPostulationFromStudent)
postulations.DELETE("/", controllers.RetirePostulation)
}
diff --git a/backend/tests/auth_student_test.go b/backend/tests/auth_student_test.go
index fdd39a4a..70b0a534 100644
--- a/backend/tests/auth_student_test.go
+++ b/backend/tests/auth_student_test.go
@@ -51,9 +51,32 @@ func TestLogin(t *testing.T) { // no es necesario eliminar usuarios.
router.ServeHTTP(w, req)
- fmt.Println(w.Body.String())
// Comprueba la respuesta HTTP y el cuerpo de la respuesta
assert.Equal(t, http.StatusOK, w.Code, "Status code is not 200")
+
+ // get to /api/users to get the user data sending the token from login
+ var LoginResponse struct {
+ Status int `json:"status"`
+ Message string `json:"message"`
+ Data struct {
+ Role string `json:"role"`
+ Token string `json:"token"`
+ } `json:"data"`
+ }
+
+ err := json.Unmarshal(w.Body.Bytes(), &LoginResponse)
+ assert.NoError(t, err, "Error unmarshalling login response")
+
+ w = httptest.NewRecorder()
+
+ req = httptest.NewRequest("GET", "/api/users/", nil)
+
+ req.Header.Set("Authorization", "Bearer "+LoginResponse.Data.Token)
+
+ router.ServeHTTP(w, req)
+
+ assert.Equal(t, http.StatusOK, w.Code, "Status code is not 200")
+
}
func TestNewStudent(t *testing.T) {
@@ -90,7 +113,7 @@ func TestUpdateStudent(t *testing.T) {
assert.Equal(t, http.StatusOK, w.Code, "Status code is not 200")
// Paso 2: Extraer el token de la respuesta
- var loginResponse struct {
+ var LoginResponse struct {
Status int `json:"status"`
Message string `json:"message"`
Data struct {
@@ -99,7 +122,7 @@ func TestUpdateStudent(t *testing.T) {
} `json:"data"`
}
- err := json.Unmarshal(w.Body.Bytes(), &loginResponse)
+ err := json.Unmarshal(w.Body.Bytes(), &LoginResponse)
assert.NoError(t, err, "Error unmarshalling login response")
// Paso 3: Usar el token para hacer la actualización del estudiante
@@ -109,7 +132,7 @@ func TestUpdateStudent(t *testing.T) {
body = bytes.NewBufferString(jsonData)
req = httptest.NewRequest("PUT", "/api/students/update", body)
- req.Header.Set("Authorization", "Bearer "+loginResponse.Data.Token)
+ req.Header.Set("Authorization", "Bearer "+LoginResponse.Data.Token)
router.ServeHTTP(w, req)
diff --git a/backend/tests/load/main.py b/backend/tests/load/main.py
index 956529d6..651accb0 100644
--- a/backend/tests/load/main.py
+++ b/backend/tests/load/main.py
@@ -2,15 +2,21 @@
import concurrent.futures
# URL y JSON de solicitud
-url = "https://whole-letisha-markalbrand56.koyeb.app/api/login"
+url = "http://127.0.0.1:8080/api/login"
payload = {
- "usuario": "prueba@prueba",
- "contra": "prueba"
+ "usuario": "empresa@prueba.com",
+ "contra": "empresaprueba"
}
+fails = 0
# Función para enviar una solicitud HTTP
def send_request(url, payload):
+ # wait between 0.0 and 2.0 seconds
+ import time
+ import random
+ time.sleep(random.random() * 2)
+
try:
response = requests.post(url, json=payload)
if response.status_code == 200:
diff --git a/backend/utils/file.go b/backend/utils/file.go
index 439ae5e9..45a89759 100644
--- a/backend/utils/file.go
+++ b/backend/utils/file.go
@@ -19,9 +19,9 @@ func Contains(slice []string, item string) bool {
return false
}
-func UploadFileToServer(url string, bearer string, file *multipart.FileHeader, dst string) error {
+func UploadFileToServer(url string, bearer string, fileHeader *multipart.FileHeader, dst string) error {
// Abrir el archivo
- f, err := file.Open()
+ f, err := fileHeader.Open()
if err != nil {
return err
}
diff --git a/backend/utils/token.go b/backend/utils/token.go
index 0e4f94b9..3351f7a1 100644
--- a/backend/utils/token.go
+++ b/backend/utils/token.go
@@ -13,8 +13,6 @@ import (
func GenerateToken(username string, userType string) (string, error) {
tokenLifespan, err := strconv.Atoi(os.Getenv("TOKEN_HOUR_LIFESPAN"))
- fmt.Println("tokenLifespan: ", tokenLifespan)
-
if err != nil {
return "", err
}
@@ -30,7 +28,7 @@ func GenerateToken(username string, userType string) (string, error) {
}
func TokenValid(c *gin.Context) error {
- tokenString := ExtractToken(c)
+ tokenString := ExtractTokenFromRequest(c)
_, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
@@ -43,7 +41,7 @@ func TokenValid(c *gin.Context) error {
return nil
}
-func ExtractToken(c *gin.Context) string {
+func ExtractTokenFromRequest(c *gin.Context) string {
token := c.Query("token")
if token != "" {
return token
@@ -55,9 +53,9 @@ func ExtractToken(c *gin.Context) string {
return ""
}
-func ExtractTokenUsername(c *gin.Context) (string, error) {
+func TokenExtractUsername(c *gin.Context) (string, error) {
- tokenString := ExtractToken(c)
+ tokenString := ExtractTokenFromRequest(c)
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
@@ -76,9 +74,9 @@ func ExtractTokenUsername(c *gin.Context) (string, error) {
return "", nil
}
-func ExtractTokenUserType(c *gin.Context) (string, error) {
+func TokenExtractRole(c *gin.Context) (string, error) {
- tokenString := ExtractToken(c)
+ tokenString := ExtractTokenFromRequest(c)
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])