Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a9f235a
API-121-feat: add delete user with bkp
JoaoMatheusLamao Nov 7, 2025
73ed363
API-121-feat: add delete user with bkp
JoaoMatheusLamao Nov 10, 2025
aa9533c
API-121-feat: add login with microsoft account
JoaoMatheusLamao Nov 13, 2025
6af7c26
API-121-refactor: change example request body format
JoaoMatheusLamao Nov 13, 2025
b3e53f0
API-121-refactor: change example request body format
JoaoMatheusLamao Nov 13, 2025
a5aa5c5
API-121-fix: update to ignore role
JoaoMatheusLamao Nov 16, 2025
d04c907
API-121-fix: update user control
JoaoMatheusLamao Nov 16, 2025
785c0ea
API-110-feat: Implement Terms of Use Management and User Consent Feat…
eduardofpaula Nov 17, 2025
095892d
API-110-feat:termos
eduardofpaula Nov 19, 2025
de3971f
API-110-refactor: create terms and user view yourself term
eduardofpaula Nov 20, 2025
1fd70ff
API-110-fix: remove .env path
eduardofpaula Nov 21, 2025
055a625
API-110-fix: desactive all terms and active correct term
eduardofpaula Nov 21, 2025
0acc66c
API-110-fix: lint errors
eduardofpaula Nov 21, 2025
abe7ff2
API-110-fix: correct formatting of UserTermConsent struct fields
eduardofpaula Nov 21, 2025
12ad79f
API-110-fix: update SonarQube action version and correct project key
eduardofpaula Nov 21, 2025
44d878b
Merge pull request #10 from iNineBD/API-110-Implementar-Termo-de-Uso-…
Andre-Bernardes200 Nov 21, 2025
60539b0
API-110-fix: move DeleteUser endpoint to authenticated routes
eduardofpaula Nov 21, 2025
0f1f25d
API-110-fix: implement user account deletion for authenticated users …
eduardofpaula Nov 21, 2025
e556f17
Merge branch 'develop' of https://github.com/iNineBD/VisionData-6Sem2…
Andre-Bernardes200 Nov 22, 2025
47ec185
API-121-feat: add endpoint to register user consent for terms of use
Andre-Bernardes200 Nov 22, 2025
c5b2769
API-121-feat: update ChangePassword function to use JWT claims for us…
Andre-Bernardes200 Nov 22, 2025
1037178
API-121-feat: enhance Microsoft login flow with forced re-authenticat…
eduardofpaula Nov 22, 2025
458d044
Merge pull request #11 from iNineBD/API-121-cadastro-com-niveis-de-us…
Andre-Bernardes200 Nov 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,7 @@ jobs:
with:
name: coverage
- name: SonarQube Scan
uses: sonarsource/sonarqube-scan-action@master
uses: SonarSource/sonarqube-scan-action@v6

env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
20 changes: 20 additions & 0 deletions clean.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash

# Script para limpar Docker completamente

echo "Parando todos os containers..."
docker stop $(docker ps -aq) 2>/dev/null || echo "Nenhum container em execução."

echo "Removendo todos os containers..."
docker rm -f $(docker ps -aq) 2>/dev/null || echo "Nenhum container para remover."

echo "Removendo todas as imagens..."
docker rmi -f $(docker images -q) 2>/dev/null || echo "Nenhuma imagem para remover."

echo "Removendo todos os volumes..."
docker volume rm $(docker volume ls -q) 2>/dev/null || echo "Nenhum volume para remover."

echo "Executando prune do sistema..."
docker system prune -a --volumes -f

echo "Limpeza completa do Docker!"
2 changes: 1 addition & 1 deletion cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
// @host localhost:8080
// @BasePath /

// @securityDefinitions.BearerAuth
// @securityDefinitions.apiKey BearerAuth
// @in header
// @type http
// @name Authorization
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/gin-contrib/cors v1.7.3
github.com/gin-gonic/gin v1.10.0
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1
github.com/redis/go-redis/v9 v9.7.0
Expand All @@ -17,6 +18,8 @@ require (
github.com/swaggo/swag v1.16.6
github.com/unrolled/secure v1.17.0
go.mongodb.org/mongo-driver v1.17.1
golang.org/x/crypto v0.42.0
golang.org/x/oauth2 v0.33.0
golang.org/x/sync v0.17.0
gorm.io/driver/sqlserver v1.6.1
gorm.io/gorm v1.31.0
Expand Down Expand Up @@ -77,7 +80,6 @@ require (
go.opentelemetry.io/otel/sdk v1.29.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
golang.org/x/arch v0.12.0 // indirect
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/mod v0.28.0 // indirect
golang.org/x/net v0.44.0 // indirect
golang.org/x/sys v0.36.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down
25 changes: 24 additions & 1 deletion internal/middleware/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func DecodeTokenJWT(token string) (jwt.MapClaims, error) {
}

// Auth is a middleware function that checks for a valid JWT token in the Authorization header
func Auth() gin.HandlerFunc {
func Auth(minAccesScope int64) gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
Expand All @@ -88,6 +88,29 @@ func Auth() gin.HandlerFunc {
return
}

if claims["role"] == nil {
authError := dto.NewAuthErrorResponse(c, "Invalid token: missing role")
c.AbortWithStatusJSON(http.StatusUnauthorized, authError)
return
}

/*userRoleInt, ok := claims["role"].(int64)
if !ok {
userRoleFloatConv, okConv := claims["role"].(float64)
if !okConv {
authError := dto.NewAuthErrorResponse(c, "Invalid token: invalid role type")
c.AbortWithStatusJSON(http.StatusUnauthorized, authError)
return
}
userRoleInt = int64(userRoleFloatConv)
}

if userRoleInt > minAccesScope {
authError := dto.NewAuthErrorResponse(c, "Insufficient permissions")
c.AbortWithStatusJSON(http.StatusForbidden, authError)
return
}*/

c.Set("currentUser", claims)
c.Next()
}
Expand Down
12 changes: 6 additions & 6 deletions internal/middleware/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,12 @@ func LoggerMiddleware(esLogger *logger.ElasticsearchLogger, config ...Middleware

// Build HTTP context
httpContext := &logger.HTTPContext{
Method: c.Request.Method,
URL: c.Request.URL.String(),
Path: c.Request.URL.Path,
Query: c.Request.URL.RawQuery,
UserAgent: c.Request.UserAgent(),
RemoteIP: c.ClientIP(),
Method: c.Request.Method,
URL: c.Request.URL.String(),
Path: c.Request.URL.Path,
Query: c.Request.URL.RawQuery,
UserAgent: c.Request.UserAgent(),
// RemoteIP: c.ClientIP(),
Headers: headers,
StatusCode: statusCode,
ResponseSize: int64(c.Writer.Size()),
Expand Down
130 changes: 130 additions & 0 deletions internal/models/dto/terms_of_use.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package dto

import "time"

// TermsOfUseResponse representa a resposta com informações de um termo de uso
type TermsOfUseResponse struct {
Id int `json:"id"`
Version string `json:"version"`
Title string `json:"title"`
Description *string `json:"description,omitempty"`
Content string `json:"content"`
IsActive bool `json:"isActive"`
EffectiveDate time.Time `json:"effectiveDate"`
CreatedAt time.Time `json:"createdAt"`
Items []TermItemResponse `json:"items,omitempty"`
}

// TermItemResponse representa a resposta com informações de um item de termo
type TermItemResponse struct {
Id int `json:"id"`
TermId int `json:"termId"`
ItemOrder int `json:"itemOrder"`
Title string `json:"title"`
Content string `json:"content"`
IsMandatory bool `json:"isMandatory"`
IsActive bool `json:"isActive"`
}

// CreateTermRequest representa a requisição para criar um novo termo
type CreateTermRequest struct {
Version string `json:"version" binding:"required"`
Title string `json:"title" binding:"required"`
Description *string `json:"description,omitempty"`
Content string `json:"content" binding:"required"`
EffectiveDate *time.Time `json:"effectiveDate,omitempty"`
Items []CreateTermItemRequest `json:"items" binding:"required,min=1"`
}

// CreateTermItemRequest representa a requisição para criar um item de termo
type CreateTermItemRequest struct {
ItemOrder int `json:"itemOrder" binding:"required,min=1"`
Title string `json:"title" binding:"required"`
Content string `json:"content" binding:"required"`
IsMandatory bool `json:"isMandatory"`
}

// UpdateTermRequest representa a requisição para atualizar um termo
type UpdateTermRequest struct {
Title *string `json:"title,omitempty"`
Description *string `json:"description,omitempty"`
Content *string `json:"content,omitempty"`
IsActive *bool `json:"isActive,omitempty"`
EffectiveDate *time.Time `json:"effectiveDate,omitempty"`
}

// UserConsentRequest representa a requisição de consentimento do usuário
type UserConsentRequest struct {
TermId int `json:"termId" binding:"required"`
ItemConsents []UserItemConsentRequest `json:"itemConsents" binding:"required,min=1"`
}

// UserItemConsentRequest representa o consentimento para um item específico
type UserItemConsentRequest struct {
ItemId int `json:"itemId" binding:"required"`
Accepted bool `json:"accepted"`
}

// UserConsentResponse representa a resposta do consentimento registrado
type UserConsentResponse struct {
Id int `json:"id"`
UserId int `json:"userId"`
TermId int `json:"termId"`
TermVersion string `json:"termVersion"`
ConsentDate time.Time `json:"consentDate"`
IsActive bool `json:"isActive"`
ItemConsents []UserItemConsentResponse `json:"itemConsents,omitempty"`
}

// UserItemConsentResponse representa a resposta do consentimento de item
type UserItemConsentResponse struct {
ItemId int `json:"itemId"`
ItemTitle string `json:"itemTitle"`
Accepted bool `json:"accepted"`
IsMandatory bool `json:"isMandatory"`
}

// RevokeConsentRequest representa a requisição para revogar consentimento
type RevokeConsentRequest struct {
TermId int `json:"termId" binding:"required"`
Reason *string `json:"reason,omitempty"`
}

// UserConsentStatusResponse representa o status de consentimento do usuário
type UserConsentStatusResponse struct {
UserId int `json:"userId"`
HasActiveConsent bool `json:"hasActiveConsent"`
CurrentTermId *int `json:"currentTermId,omitempty"`
CurrentTermVersion *string `json:"currentTermVersion,omitempty"`
CurrentTermTitle *string `json:"currentTermTitle,omitempty"`
ConsentDate *time.Time `json:"consentDate,omitempty"`
NeedsNewConsent bool `json:"needsNewConsent"`
}

// MyConsentStatusResponse representa o status completo de consentimento do usuário com termo e itens
type MyConsentStatusResponse struct {
UserId int `json:"userId"`
HasActiveConsent bool `json:"hasActiveConsent"`
NeedsNewConsent bool `json:"needsNewConsent"`
Term *TermsOfUseResponse `json:"term,omitempty"`
ConsentDate *time.Time `json:"consentDate,omitempty"`
}

// ListTermsResponse representa a lista de termos (para admin)
type ListTermsResponse struct {
Terms []TermsOfUseResponse `json:"terms"`
TotalCount int `json:"totalCount"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
}

// TermStatisticsResponse representa estatísticas de consentimento de um termo
type TermStatisticsResponse struct {
TermId int `json:"termId"`
TermVersion string `json:"termVersion"`
TotalUsers int `json:"totalUsers"`
UsersWithConsent int `json:"usersWithConsent"`
UsersWithoutConsent int `json:"usersWithoutConsent"`
ConsentRate float64 `json:"consentRate"` // Porcentagem
LastUpdate time.Time `json:"lastUpdate"`
}
23 changes: 13 additions & 10 deletions internal/models/dto/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@ import "time"

// CreateUserRequest representa a requisição de criação de usuário
type CreateUserRequest struct {
Name string `json:"name" binding:"required,min=3,max=200" example:"João Silva"`
Email string `json:"email" binding:"required,email,max=255" example:"joao.silva@example.com"`
Password *string `json:"password,omitempty" binding:"omitempty,min=8,max=100" example:"SenhaSegura@123"`
UserType string `json:"userType" binding:"required,oneof=ADMIN MANAGER AGENT VIEWER" example:"AGENT" enums:"ADMIN,MANAGER,AGENT,VIEWER"`
MicrosoftId *string `json:"microsoftId,omitempty" binding:"omitempty,max=255" example:"a1b2c3d4-e5f6-7890-abcd-ef1234567890"`
Name string `json:"name" binding:"required,min=3,max=200" example:"João Silva"`
Email string `json:"email" binding:"required,email,max=255" example:"joao@example.com"`
Password *string `json:"password,omitempty" binding:"omitempty,min=8,max=100" example:"SenhaSegura@123"`
UserType string `json:"userType" binding:"required,oneof=ADMIN MANAGER SUPPORT" example:"SUPPORT" enums:"ADMIN,MANAGER,SUPPORT"`
TermConsent UserConsentRequest `json:"termConsent" binding:"required"` // Consentimento obrigatório
// MicrosoftId *string `json:"microsoftId,omitempty" binding:"omitempty,max=255" example:"a1b2c3d4-e5f6-7890-abcd-ef1234567890"`
}

// UpdateUserRequest representa a requisição de atualização de usuário
type UpdateUserRequest struct {
Name *string `json:"name,omitempty" binding:"omitempty,min=3,max=200" example:"João Silva Atualizado"`
Email *string `json:"email,omitempty" binding:"omitempty,email,max=255" example:"joao.novo@example.com"`
Password *string `json:"password,omitempty" binding:"omitempty,min=8,max=100" example:"NovaSenha@456"`
UserType *string `json:"userType,omitempty" binding:"omitempty,oneof=ADMIN MANAGER AGENT VIEWER" example:"MANAGER" enums:"ADMIN,MANAGER,AGENT,VIEWER"`
UserType *string `json:"userType,omitempty" binding:"omitempty,oneof=ADMIN MANAGER SUPPORT" example:"MANAGER" enums:"ADMIN,MANAGER,SUPPORT"`
IsActive *bool `json:"isActive,omitempty" example:"true"`
}

Expand All @@ -36,8 +37,10 @@ type ChangePasswordRequest struct {

// LoginRequest representa a requisição de login
type LoginRequest struct {
Email string `json:"email" binding:"required,email" example:"joao.silva@example.com"`
Password string `json:"password" binding:"required" example:"SenhaSegura@123"`
Email string `json:"email" binding:"required,email" example:"joao@example.com"`
Password string `json:"password" binding:"required_if=LoginType password" example:"SenhaSegura@123"`
LoginType string `json:"login_type" binding:"required,oneof=password microsoft" example:"password"`
MicrosoftIDToken *string `json:"microsoft_id_token,omitempty" example:"eyJhbGciOi..."` // optional for microsoft flow when front handles OAuth; not needed when backend-only
}

// MicrosoftAuthRequest representa a requisição de autenticação Microsoft
Expand All @@ -53,8 +56,8 @@ type MicrosoftAuthRequest struct {
type UserResponse struct {
Id int `json:"id" example:"1"`
Name string `json:"name" example:"João Silva"`
Email string `json:"email" example:"joao.silva@example.com"`
UserType string `json:"userType" example:"AGENT" enums:"ADMIN,MANAGER,AGENT,VIEWER"`
Email string `json:"email" example:"joao@example.com"`
UserType string `json:"userType" example:"SUPPORT" enums:"ADMIN,MANAGER,SUPPORT"`
MicrosoftId *string `json:"microsoftId,omitempty" example:"a1b2c3d4-e5f6-7890-abcd-ef1234567890"`
IsActive bool `json:"isActive" example:"true"`
CreatedAt time.Time `json:"createdAt" example:"2025-10-16T10:30:00Z"`
Expand Down
84 changes: 84 additions & 0 deletions internal/models/entities/terms_of_use.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package entities

import "time"

// TermsOfUse representa um termo de uso com versionamento
type TermsOfUse struct {
Id int `json:"id" gorm:"column:Id;primaryKey;autoIncrement"`
Version string `json:"version" gorm:"column:Version;type:nvarchar(50);not null;unique"`
Content string `json:"content" gorm:"column:Content;type:text;not null"`
Title string `json:"title" gorm:"column:Title;type:nvarchar(500);not null"`
Description *string `json:"description,omitempty" gorm:"column:Description;type:nvarchar(max)"`
IsActive bool `json:"isActive" gorm:"column:IsActive;type:bit;not null;default:1"`
EffectiveDate time.Time `json:"effectiveDate" gorm:"column:EffectiveDate;type:datetime2;not null;default:GETDATE()"`
CreatedAt time.Time `json:"createdAt" gorm:"column:CreatedAt;type:datetime2;not null;default:GETDATE()"`
CreatedBy *int `json:"createdBy,omitempty" gorm:"column:CreatedBy;type:int"`
UpdatedAt *time.Time `json:"updatedAt,omitempty" gorm:"column:UpdatedAt;type:datetime2"`
UpdatedBy *int `json:"updatedBy,omitempty" gorm:"column:UpdatedBy;type:int"`

// Relacionamentos
Items []TermItem `json:"items,omitempty" gorm:"foreignKey:TermId"`
}

// TableName especifica o nome da tabela no banco
func (TermsOfUse) TableName() string {
return "dbo.TermsOfUse"
}

// TermItem representa um item de um termo (obrigatório ou opcional)
type TermItem struct {
Id int `json:"id" gorm:"column:Id;primaryKey;autoIncrement"`
TermId int `json:"termId" gorm:"column:TermId;type:int;not null"`
ItemOrder int `json:"itemOrder" gorm:"column:ItemOrder;type:int;not null;default:1"`
Title string `json:"title" gorm:"column:Title;type:nvarchar(500);not null"`
Content string `json:"content" gorm:"column:Content;type:text;not null"`
IsMandatory bool `json:"isMandatory" gorm:"column:IsMandatory;type:bit;not null;default:0"`
IsActive bool `json:"isActive" gorm:"column:IsActive;type:bit;not null;default:1"`
CreatedAt time.Time `json:"createdAt" gorm:"column:CreatedAt;type:datetime2;not null;default:GETDATE()"`
UpdatedAt *time.Time `json:"updatedAt,omitempty" gorm:"column:UpdatedAt;type:datetime2"`
}

// TableName especifica o nome da tabela no banco
func (TermItem) TableName() string {
return "dbo.TermItems"
}

// UserTermConsent representa o consentimento de um usuário para um termo
type UserTermConsent struct {
Id int `json:"id" gorm:"column:Id;primaryKey;autoIncrement"`
UserId int `json:"userId" gorm:"column:UserId;type:int;not null"`
TermId int `json:"termId" gorm:"column:TermId;type:int;not null"`
ConsentDate time.Time `json:"consentDate" gorm:"column:ConsentDate;type:datetime2;not null;default:GETDATE()"`
IsActive bool `json:"isActive" gorm:"column:IsActive;type:bit;not null;default:1"`
IPAddress *string `json:"ipAddress,omitempty" gorm:"column:IPAddress;type:nvarchar(50)"`
UserAgent *string `json:"userAgent,omitempty" gorm:"column:UserAgent;type:nvarchar(500)"`
RevokedAt *time.Time `json:"revokedAt,omitempty" gorm:"column:RevokedAt;type:datetime2"`
RevokedReason *string `json:"revokedReason,omitempty" gorm:"column:RevokedReason;type:nvarchar(max)"`

// Relacionamentos
User User `json:"user,omitempty" gorm:"foreignKey:UserId"`
Term TermsOfUse `json:"term,omitempty" gorm:"foreignKey:TermId"`
ItemConsents []UserItemConsent `json:"itemConsents,omitempty" gorm:"foreignKey:UserConsentId"`
}

// TableName especifica o nome da tabela no banco
func (UserTermConsent) TableName() string {
return "dbo.UserTermConsents"
}

// UserItemConsent representa o consentimento de um usuário para um item específico
type UserItemConsent struct {
Id int `json:"id" gorm:"column:Id;primaryKey;autoIncrement"`
UserConsentId int `json:"userConsentId" gorm:"column:UserConsentId;type:int;not null"`
ItemId int `json:"itemId" gorm:"column:ItemId;type:int;not null"`
Accepted bool `json:"accepted" gorm:"column:Accepted;type:bit;not null"`
ConsentDate time.Time `json:"consentDate" gorm:"column:ConsentDate;type:datetime2;not null;default:GETDATE()"`

// Relacionamentos
Item TermItem `json:"item,omitempty" gorm:"foreignKey:ItemId"`
}

// TableName especifica o nome da tabela no banco
func (UserItemConsent) TableName() string {
return "dbo.UserItemConsents"
}
Loading
Loading