diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..c3e7a7f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,62 @@ +## Descrição + +Este pull request introduz melhorias no template HTML da página de erro. A principal melhoria é o aumento do tamanho da fonte do código de erro e da mensagem para torná-los mais destacados e fáceis de ler. + +## Mudanças Realizadas + +1. **Aumento do Tamanho da Fonte**: + - Adicionadas estilos CSS personalizados para aumentar o tamanho da fonte do código de erro e da mensagem. + - Aplicadas classes utilitárias do Tailwind CSS para garantir que o texto esteja centralizado. + +### Arquivos Modificados + +- `templates/web/error.html`: Estrutura HTML atualizada e adição de estilos personalizados. + +### Mudanças Detalhadas + +- **Tamanho da Fonte do Código de Erro**: + - Adicionada a classe CSS `.error-code` com `font-size: 4rem;` para aumentar o tamanho da fonte do código de erro. +- **Tamanho da Fonte da Mensagem de Erro**: + - Adicionada a classe CSS `.error-message` com `font-size: 1.5rem;` para aumentar o tamanho da fonte da mensagem de erro. +- **Alinhamento Central**: + - Aplicada a classe `text-center` do Tailwind CSS para centralizar o texto dentro do contêiner. + +## Antes e Depois + +### Antes + +![Screenshot Antes](link_para_screenshot_antes) + +### Depois + +![Screenshot Depois](link_para_screenshot_depois) + +## Motivação e Contexto + +O tamanho da fonte anterior para o código de erro e a mensagem era muito pequeno, dificultando para os usuários identificarem e entenderem rapidamente o erro. Aumentar o tamanho da fonte melhora a legibilidade e a experiência do usuário. + +## Como Isso Foi Testado? + +- Testado manualmente a página de erro para garantir que os novos tamanhos de fonte sejam aplicados corretamente e o texto permaneça centralizado. + +## Issue Relacionada + +- [Issue #123](link_para_issue_relacionada) + +## Tipos de Mudanças + +- [ ] Correção de bug (mudança que não quebra a compatibilidade e corrige um problema) +- [x] Nova funcionalidade (mudança que não quebra a compatibilidade e adiciona uma funcionalidade) +- [ ] Mudança que quebra a compatibilidade (correção ou funcionalidade que causa uma mudança em funcionalidades existentes) + +## Checklist + +- [x] Meu código segue o estilo de código deste projeto. +- [x] Minha mudança requer uma mudança na documentação. +- [x] Eu atualizei a documentação conforme necessário. +- [ ] Eu adicionei testes para cobrir minhas mudanças. +- [x] Todos os novos e antigos testes passaram. + +## Notas Adicionais + +- Qualquer informação ou contexto adicional que os revisores possam precisar saber. diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go index c33507d..4ac4f54 100644 --- a/backend/cmd/api/main.go +++ b/backend/cmd/api/main.go @@ -2,35 +2,60 @@ package main import ( "faladev/config" - "faladev/internal/models" + "faladev/internal/repository" + "faladev/internal/services" "faladev/pkg/http" "fmt" + "os" log "github.com/sirupsen/logrus" + "faladev/internal/database" + "golang.org/x/oauth2" ) func main() { - oauthConfig := config.SetupOAuthConfig() + log.SetOutput(os.Stdout) + log.SetLevel(log.DebugLevel) - token, err := models.LoadToken() + appConfig, err := config.LoadConfig() if err != nil { - log.Fatal(err) + log.Fatalf("Failed to load configuration: %v", err) + } + + appOAuth2Config := appConfig.OAuth2.Config + + db, err := database.InitDB(appConfig.DatabaseURL) + + if err != nil { + log.Fatalf("Failed to initialize database: %v", err) + } + + tokenRepo := repository.NewTokenRepository(db) + studentRepo := repository.NewStudentRepository(db) + eventRepo := repository.NewEventRepository(db) + + tokenService := services.NewTokenService(tokenRepo) + studentService := services.NewStudentService(studentRepo) + eventService := services.NewEventService(eventRepo) + + token, err := tokenService.GetToken() + + if err != nil { + log.Fatalf("Failed to load token: %v", err) } if token == nil { log.Println("Token not found, redirecting to authentication...") - fmt.Println("Please visit the following link to authorize your Google account: ", oauthConfig.AuthCodeURL("state-token", oauth2.AccessTypeOffline)) + fmt.Println("Please visit the following link to authorize your Google account: ", appOAuth2Config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)) } - appAuthConfig := config.AuthConfig{ - Config: oauthConfig, - Token: token, - } + calendarService := services.NewGoogleCalendarService() + emailService := services.NewGmailService(appOAuth2Config, token) - http.StartServer(appAuthConfig) + http.StartServer(appOAuth2Config, *studentService, calendarService, emailService, *tokenService, *eventService) } diff --git a/backend/cmd/migrate/main.go b/backend/cmd/migrate/main.go index 2900fae..33fcab4 100644 --- a/backend/cmd/migrate/main.go +++ b/backend/cmd/migrate/main.go @@ -1,6 +1,7 @@ package main import ( + "faladev/config" "faladev/internal/database" "faladev/internal/models" "fmt" @@ -11,7 +12,17 @@ func main() { fmt.Println("Starting migration...") - db := database.GetDB() + appConfig, err := config.LoadConfig() + + if err != nil { + log.Fatalf("Failed to load configuration: %v", err) + } + + db, err := database.InitDB(appConfig.DatabaseURL) + + if err != nil { + log.Fatalf("Failed to initialize database: %v", err) + } db.Exec("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";") @@ -24,8 +35,18 @@ func main() { errToken := db.AutoMigrate(&models.Token{}) if errToken != nil { - log.Fatalf("Failed to migrate students: %v", errToken) + log.Fatalf("Failed to migrate tokens: %v", errToken) + } + + errEvent := db.AutoMigrate(&models.Event{}) + + if errEvent != nil { + log.Fatalf("Failed to migrate events: %v", errEvent) } fmt.Println("Migration completed successfully!") + + database.Seed(db) + + fmt.Println("Seed completed successfully!") } diff --git a/backend/config/config.go b/backend/config/config.go index 1b2d6bb..a93941e 100644 --- a/backend/config/config.go +++ b/backend/config/config.go @@ -1,6 +1,7 @@ package config import ( + "errors" "log" "os" @@ -11,29 +12,25 @@ import ( "google.golang.org/api/gmail/v1" ) -type AuthConfig struct { +type OAuth2Config struct { Config *oauth2.Config Token *oauth2.Token } -func GetEventGoogleMeet() string { - - meetEvent := os.Getenv("GOOGLE_MEET_EVENT") +type Config struct { + DatabaseURL string + OAuth2 OAuth2Config +} - if meetEvent == "" { - log.Fatal("Please set the GOOGLE_MEET_EVENT environment variable") +func getDatabaseURL() string { + databaseURL := os.Getenv("DATABASE_URL") + if databaseURL == "" { + log.Fatal("Please set the DATABASE_URL environment variable") } - - return meetEvent + return databaseURL } -func SetupOAuthConfig() *oauth2.Config { - - err := godotenv.Load() - - if err != nil { - log.Printf("Warning: No .env file found, reading from environment variables. Error: %v", err) - } +func getOAuthConfig() *oauth2.Config { clientID := os.Getenv("GOOGLE_CLIENT_ID") clientSecret := os.Getenv("GOOGLE_CLIENT_SECRET") @@ -51,3 +48,20 @@ func SetupOAuthConfig() *oauth2.Config { Endpoint: google.Endpoint, } } + +func LoadConfig() (*Config, error) { + + err := godotenv.Load() + + if err != nil { + return nil, errors.New("failed to load environment variables") + } + + oauthConfig := getOAuthConfig() + databaseURL := getDatabaseURL() + + return &Config{ + DatabaseURL: databaseURL, + OAuth2: OAuth2Config{Config: oauthConfig}, + }, nil +} diff --git a/backend/internal/database/database.go b/backend/internal/database/database.go index 3415285..3adefc3 100644 --- a/backend/internal/database/database.go +++ b/backend/internal/database/database.go @@ -2,7 +2,6 @@ package database import ( "log" - "os" "sync" "time" @@ -17,35 +16,32 @@ var ( once sync.Once ) -func GetDB() *gorm.DB { +func InitDB(databaseURL string) (*gorm.DB, error) { - once.Do(func() { - - databaseURL := os.Getenv("DATABASE_URL") + var err error - if databaseURL == "" { - log.Fatal("DATABASE_URL environment variable not set") - } - - var err error + once.Do(func() { db, err = gorm.Open(postgres.Open(databaseURL), &gorm.Config{}) - if err != nil { - panic("failed to connect to database") + log.Printf("failed to connect to database: %v", err) + return } sqlDB, err := db.DB() - if err != nil { - panic("failed to get database connection handle") + log.Printf("failed to get database connection handle: %v", err) + return } sqlDB.SetMaxIdleConns(20) sqlDB.SetMaxOpenConns(200) sqlDB.SetConnMaxLifetime(time.Hour) - }) - return db + if err != nil { + return nil, err + } + + return db, nil } diff --git a/backend/internal/database/seed.go b/backend/internal/database/seed.go new file mode 100644 index 0000000..2bd8f04 --- /dev/null +++ b/backend/internal/database/seed.go @@ -0,0 +1,44 @@ +package database + +import ( + "faladev/internal/models" + "log" + "time" + + "gorm.io/gorm" +) + +func Seed(db *gorm.DB) { + seedEvents(db) +} + +func seedEvents(db *gorm.DB) { + + var eventCount int64 + + db.Model(&models.Event{}).Count(&eventCount) + + if eventCount == 0 { + + log.Println("Inserting default event...") + + defaultEvent := models.Event{ + Name: "Mentoria (Carreira e Tecnologia)", + Description: "", + Location: "https://meet.google.com/eam-bqde-mgd", + StartDate: time.Now().Add(24 * time.Hour), + EndDate: time.Now().Add(26 * time.Hour), + StartTime: time.Now().Add(24 * time.Hour), + EndTime: time.Now().Add(26 * time.Hour), + Organizer: "", + Email: "", + Phone: "", + } + + if err := db.Create(&defaultEvent).Error; err != nil { + log.Fatalf("Failed to insert default event: %v", err) + } + + log.Println("Default event inserted successfully!") + } +} diff --git a/backend/internal/models/events.go b/backend/internal/models/events.go new file mode 100644 index 0000000..e0650fd --- /dev/null +++ b/backend/internal/models/events.go @@ -0,0 +1,21 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +type Event struct { + gorm.Model + Name string `json:"name"` + Description string `json:"description"` + Location string `json:"location"` + StartDate time.Time `json:"start_date"` + EndDate time.Time `json:"end_date"` + StartTime time.Time `json:"start_time"` + EndTime time.Time `json:"end_time"` + Organizer string `json:"organizer"` + Email string `json:"email"` + Phone string `json:"phone"` +} diff --git a/backend/internal/models/student.go b/backend/internal/models/student.go index 425ad32..d409e51 100644 --- a/backend/internal/models/student.go +++ b/backend/internal/models/student.go @@ -1,8 +1,6 @@ package models import ( - "faladev/internal/database" - "gorm.io/gorm" ) @@ -12,31 +10,3 @@ type Student struct { Email string Phone string } - -func InsertOrUpdateStudent(name, email, phone string) error { - - db := database.GetDB() - - var existingStudent Student - err := db.Where("email = ?", email).First(&existingStudent).Error - - if err != nil { - if err == gorm.ErrRecordNotFound { - newStudent := Student{ - Name: name, - Email: email, - Phone: phone, - } - result := db.Create(&newStudent) - return result.Error - } - - return err - } - - existingStudent.Name = name - existingStudent.Phone = phone - - result := db.Save(&existingStudent) - return result.Error -} diff --git a/backend/internal/models/token.go b/backend/internal/models/token.go index 8a493b6..d089184 100644 --- a/backend/internal/models/token.go +++ b/backend/internal/models/token.go @@ -1,10 +1,6 @@ package models import ( - "encoding/json" - "faladev/internal/database" - - "golang.org/x/oauth2" "gorm.io/gorm" ) @@ -12,46 +8,3 @@ type Token struct { gorm.Model Token string } - -func SaveToken(token *oauth2.Token) error { - - db := database.GetDB() - - tokenJSON, err := json.Marshal(token) - - if err != nil { - return err - } - - newToken := Token{ - Token: string(tokenJSON), - } - - return db.Create(&newToken).Error -} - -func LoadToken() (*oauth2.Token, error) { - - db := database.GetDB() - - var tokenModel Token - - err := db.Order("created_at desc").First(&tokenModel).Error - - if err != nil { - if err == gorm.ErrRecordNotFound { - return nil, nil - } - return nil, err - } - - var token oauth2.Token - - err = json.Unmarshal([]byte(tokenModel.Token), &token) - - if err != nil { - return nil, err - } - - return &token, nil -} diff --git a/backend/internal/repository/event_repository.go b/backend/internal/repository/event_repository.go new file mode 100644 index 0000000..defed77 --- /dev/null +++ b/backend/internal/repository/event_repository.go @@ -0,0 +1,57 @@ +package repository + +import ( + "faladev/internal/models" + "time" + + "gorm.io/gorm" +) + +type EventRepository struct { + db *gorm.DB +} + +func NewEventRepository(db *gorm.DB) *EventRepository { + return &EventRepository{ + db: db, + } +} + +func (r *EventRepository) CreateEvent(event *models.Event) error { + return r.db.Create(event).Error +} + +func (r *EventRepository) GetEventByID(id uint) (*models.Event, error) { + var event models.Event + err := r.db.First(&event, id).Error + if err != nil { + return nil, err + } + return &event, nil +} + +func (r *EventRepository) UpdateEvent(event *models.Event) error { + return r.db.Save(event).Error +} + +func (r *EventRepository) DeleteEvent(id uint) error { + return r.db.Delete(&models.Event{}, id).Error +} + +func (r *EventRepository) ListEvents() ([]models.Event, error) { + var events []models.Event + err := r.db.Find(&events).Error + if err != nil { + return nil, err + } + return events, nil +} + +func (r *EventRepository) GetNextEvent() (*models.Event, error) { + var event models.Event + err := r.db.Where("start_date >= ?", time.Now()).Order("start_date, start_time").Debug().First(&event).Error + if err != nil { + return nil, err + } + return &event, nil +} diff --git a/backend/internal/repository/student_repository.go b/backend/internal/repository/student_repository.go new file mode 100644 index 0000000..66661be --- /dev/null +++ b/backend/internal/repository/student_repository.go @@ -0,0 +1,44 @@ +package repository + +import ( + "faladev/internal/models" + + "gorm.io/gorm" +) + +type StudentRepository struct { + db *gorm.DB +} + +func NewStudentRepository(db *gorm.DB) *StudentRepository { + return &StudentRepository{ + db: db, + } +} + +func (r *StudentRepository) InsertOrUpdateStudent(name, email, phone string) error { + + var existingStudent models.Student + + err := r.db.Where("email = ?", email).First(&existingStudent).Error + + if err != nil { + if err == gorm.ErrRecordNotFound { + newStudent := models.Student{ + Name: name, + Email: email, + Phone: phone, + } + result := r.db.Create(&newStudent) + return result.Error + } + return err + } + + existingStudent.Name = name + existingStudent.Phone = phone + + result := r.db.Save(&existingStudent) + + return result.Error +} diff --git a/backend/internal/repository/token_repository.go b/backend/internal/repository/token_repository.go new file mode 100644 index 0000000..6e67945 --- /dev/null +++ b/backend/internal/repository/token_repository.go @@ -0,0 +1,58 @@ +package repository + +import ( + "encoding/json" + "faladev/internal/models" + + "golang.org/x/oauth2" + "gorm.io/gorm" +) + +type TokenRepository struct { + db *gorm.DB +} + +func NewTokenRepository(db *gorm.DB) *TokenRepository { + return &TokenRepository{ + db: db, + } +} + +func (r *TokenRepository) CreateToken(token *oauth2.Token) error { + + tokenJSON, err := json.Marshal(token) + + if err != nil { + return err + } + + newToken := models.Token{ + Token: string(tokenJSON), + } + + return r.db.Create(&newToken).Error +} + +func (r *TokenRepository) GetToken() (*oauth2.Token, error) { + + var tokenModel models.Token + + err := r.db.Order("created_at desc").First(&tokenModel).Error + + if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } + return nil, err + } + + var token oauth2.Token + + err = json.Unmarshal([]byte(tokenModel.Token), &token) + + if err != nil { + return nil, err + } + + return &token, nil +} diff --git a/backend/internal/services/event_service.go b/backend/internal/services/event_service.go new file mode 100644 index 0000000..805d047 --- /dev/null +++ b/backend/internal/services/event_service.go @@ -0,0 +1,40 @@ +package services + +import ( + "faladev/internal/models" + "faladev/internal/repository" +) + +type EventService struct { + repo *repository.EventRepository +} + +func NewEventService(repo *repository.EventRepository) *EventService { + return &EventService{ + repo: repo, + } +} + +func (s *EventService) CreateEvent(event *models.Event) error { + return s.repo.CreateEvent(event) +} + +func (s *EventService) GetEventByID(id uint) (*models.Event, error) { + return s.repo.GetEventByID(id) +} + +func (s *EventService) UpdateEvent(event *models.Event) error { + return s.repo.UpdateEvent(event) +} + +func (s *EventService) DeleteEvent(id uint) error { + return s.repo.DeleteEvent(id) +} + +func (s *EventService) ListEvents() ([]models.Event, error) { + return s.repo.ListEvents() +} + +func (s *EventService) GetNextEvent() (*models.Event, error) { + return s.repo.GetNextEvent() +} diff --git a/backend/internal/services/student_service.go b/backend/internal/services/student_service.go new file mode 100644 index 0000000..e3f6980 --- /dev/null +++ b/backend/internal/services/student_service.go @@ -0,0 +1,19 @@ +package services + +import ( + "faladev/internal/repository" +) + +type StudentService struct { + repo *repository.StudentRepository +} + +func NewStudentService(repo *repository.StudentRepository) *StudentService { + return &StudentService{ + repo: repo, + } +} + +func (s *StudentService) InsertOrUpdateStudent(name, email, phone string) error { + return s.repo.InsertOrUpdateStudent(name, email, phone) +} diff --git a/backend/internal/services/token_service.go b/backend/internal/services/token_service.go new file mode 100644 index 0000000..261483e --- /dev/null +++ b/backend/internal/services/token_service.go @@ -0,0 +1,25 @@ +package services + +import ( + "faladev/internal/repository" + + "golang.org/x/oauth2" +) + +type TokenService struct { + repo *repository.TokenRepository +} + +func NewTokenService(repo *repository.TokenRepository) *TokenService { + return &TokenService{ + repo: repo, + } +} + +func (s *TokenService) CreateToken(token *oauth2.Token) error { + return s.repo.CreateToken(token) +} + +func (s *TokenService) GetToken() (*oauth2.Token, error) { + return s.repo.GetToken() +} diff --git a/backend/pkg/http/http.go b/backend/pkg/http/http.go index 684201e..1124a76 100644 --- a/backend/pkg/http/http.go +++ b/backend/pkg/http/http.go @@ -3,9 +3,7 @@ package http import ( "context" docs "faladev/cmd/docs" - "faladev/config" "faladev/internal/auth" - "faladev/internal/models" "faladev/internal/services" "fmt" "html/template" @@ -27,13 +25,19 @@ type App struct { config *oauth2.Config calendarService services.CalendarService emailService services.EmailService + studentService services.StudentService + tokenService services.TokenService + eventService services.EventService } -func NewApp(config *oauth2.Config, calendar services.CalendarService, email services.EmailService) *App { +func NewApp(config *oauth2.Config, calendar services.CalendarService, email services.EmailService, studentService services.StudentService, tokenService services.TokenService, eventService services.EventService) *App { return &App{ config: config, calendarService: calendar, emailService: email, + studentService: studentService, + tokenService: tokenService, + eventService: eventService, } } @@ -73,12 +77,12 @@ func (app *App) EventHandler(c *gin.Context) { email := c.PostForm("email") phone := c.PostForm("phone") - if err := models.InsertOrUpdateStudent(name, email, phone); err != nil { + if err := app.studentService.InsertOrUpdateStudent(name, email, phone); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Error inserting record into database: " + err.Error()}) return } - token, err := models.LoadToken() + token, err := app.tokenService.GetToken() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load token: " + err.Error()}) @@ -94,7 +98,7 @@ func (app *App) EventHandler(c *gin.Context) { return } - err = models.SaveToken(token) + err = app.tokenService.CreateToken(token) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save token: " + err.Error()}) @@ -109,8 +113,14 @@ func (app *App) EventHandler(c *gin.Context) { return } - eventID := config.GetEventGoogleMeet() - eventDetails, err := app.calendarService.AddGuestToEvent(context.Background(), calendarService, eventID, email) + event, err := app.eventService.GetNextEvent() + + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Error getting next event: " + err.Error()}) + return + } + + eventDetails, err := app.calendarService.AddGuestToEvent(context.Background(), calendarService, event.Location, email) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Error getting event details: " + err.Error()}) @@ -159,7 +169,7 @@ func (app *App) OAuthCallbackHandler(c *gin.Context) { return } - err = models.SaveToken(token) + err = app.tokenService.CreateToken(token) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Unable to save token: %v", err)}) @@ -171,9 +181,9 @@ func (app *App) OAuthCallbackHandler(c *gin.Context) { c.Redirect(http.StatusSeeOther, "/") } -func StartServer(appAuthConfig config.AuthConfig) { +func StartServer(appOAuth2Config *oauth2.Config, studentService services.StudentService, calendarService services.CalendarService, emailService services.EmailService, tokenService services.TokenService, eventService services.EventService) { - app := NewApp(appAuthConfig.Config, services.NewGoogleCalendarService(), services.NewGmailService(appAuthConfig.Config, appAuthConfig.Token)) + app := NewApp(appOAuth2Config, calendarService, emailService, studentService, tokenService, eventService) router := gin.Default() diff --git a/entrypoint.sh b/entrypoint.sh index af44b90..7331e5b 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -10,4 +10,4 @@ go run cmd/migrate/main.go echo "Starting the application with reflex..." -exec reflex -r '\.go$' -s -- sh -c "dlv debug cmd/api/main.go --headless --listen=:2345 --api-version=2 --accept-multiclient --continue" \ No newline at end of file +exec reflex -r '\.go$' -s -- sh -c "go run cmd/api/main.go" \ No newline at end of file