refactor: Remove deprecated, bad examples

This commit is contained in:
Jean Jacques Avril 2025-03-10 20:48:13 +00:00
parent ce39b7ba34
commit aa5c7e77fc
5 changed files with 0 additions and 492 deletions

View File

@ -1,101 +0,0 @@
// interfaces/http/handlers/time_entry_handler.go
package main
import (
"net/http"
"github.com/email/timetracker/internal/application/timetracking"
"github.com/email/timetracker/internal/interfaces/http/dto"
"github.com/email/timetracker/internal/interfaces/http/middleware"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// TimeEntryHandler behandelt HTTP-Anfragen für Zeitbuchungen
type TimeEntryHandler struct {
createTimeEntryUseCase *timetracking.CreateTimeEntryUseCase
updateTimeEntryUseCase *timetracking.UpdateTimeEntryUseCase
listTimeEntriesUseCase *timetracking.ListTimeEntriesUseCase
deleteTimeEntryUseCase *timetracking.DeleteTimeEntryUseCase
}
// NewTimeEntryHandler erstellt einen neuen TimeEntryHandler
func NewTimeEntryHandler(
createTimeEntryUseCase *timetracking.CreateTimeEntryUseCase,
updateTimeEntryUseCase *timetracking.UpdateTimeEntryUseCase,
listTimeEntriesUseCase *timetracking.ListTimeEntriesUseCase,
deleteTimeEntryUseCase *timetracking.DeleteTimeEntryUseCase,
) *TimeEntryHandler {
return &TimeEntryHandler{
createTimeEntryUseCase: createTimeEntryUseCase,
updateTimeEntryUseCase: updateTimeEntryUseCase,
listTimeEntriesUseCase: listTimeEntriesUseCase,
deleteTimeEntryUseCase: deleteTimeEntryUseCase,
}
}
// RegisterRoutes registriert die Routen am Router
func (h *TimeEntryHandler) RegisterRoutes(router *gin.RouterGroup) {
timeEntries := router.Group("/time-entries")
{
timeEntries.GET("", h.ListTimeEntries)
timeEntries.POST("", h.CreateTimeEntry)
timeEntries.GET("/:id", h.GetTimeEntry)
timeEntries.PUT("/:id", h.UpdateTimeEntry)
timeEntries.DELETE("/:id", h.DeleteTimeEntry)
}
}
// CreateTimeEntry behandelt die Erstellung einer neuen Zeitbuchung
func (h *TimeEntryHandler) CreateTimeEntry(c *gin.Context) {
var req dto.CreateTimeEntryRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Tenant-ID aus dem Kontext extrahieren
companyID, exists := middleware.GetCompanyID(c)
if !exists {
c.JSON(http.StatusBadRequest, gin.H{"error": "Company ID not found"})
return
}
// Benutzer-ID aus dem Kontext oder Request
var userID uuid.UUID
if req.UserID != nil {
userID = *req.UserID
} else {
currentUserID, exists := middleware.GetUserID(c)
if !exists {
c.JSON(http.StatusBadRequest, gin.H{"error": "User ID not found"})
return
}
userID = currentUserID
}
// Command erstellen
cmd := timetracking.CreateTimeEntryCommand{
UserID: userID,
ProjectID: req.ProjectID,
ActivityID: req.ActivityID,
TaskID: req.TaskID,
StartTime: req.StartTime,
EndTime: req.EndTime,
Description: req.Description,
BillablePercentage: req.BillablePercentage,
}
// UseCase ausführen
result := h.createTimeEntryUseCase.Execute(c.Request.Context(), companyID, cmd)
if result.IsFailure() {
c.JSON(http.StatusInternalServerError, gin.H{"error": result.Error().Error()})
return
}
// TimeEntry in Response-DTO umwandeln
timeEntry := result.Value()
response := dto.MapTimeEntryToDTO(*timeEntry)
c.JSON(http.StatusCreated, response)
}

View File

@ -1,52 +0,0 @@
// domain/repositories/time_entry_repository.go
package repositories
import (
"context"
"time"
"github.com/email/timetracker/internal/domain/entities"
"github.com/email/timetracker/pkg/functional"
"github.com/google/uuid"
)
// TimeEntryFilter enthält Filter für die Suche nach Zeitbuchungen
type TimeEntryFilter struct {
UserID *uuid.UUID
ProjectID *uuid.UUID
CustomerID *uuid.UUID
StartDate *time.Time
EndDate *time.Time
ActivityID *uuid.UUID
TaskID *uuid.UUID
}
// TimeEntryRepository Interface für den Zugriff auf Zeitbuchungen
type TimeEntryRepository interface {
// FindByID sucht eine Zeitbuchung anhand ihrer ID
FindByID(ctx context.Context, companyID, id uuid.UUID) functional.Result[*entities.TimeEntry]
// FindAll sucht alle Zeitbuchungen mit optionalen Filtern
FindAll(ctx context.Context, companyID uuid.UUID, filter TimeEntryFilter) functional.Result[[]entities.TimeEntry]
// Create erstellt eine neue Zeitbuchung
Create(ctx context.Context, entry *entities.TimeEntry) functional.Result[*entities.TimeEntry]
// Update aktualisiert eine bestehende Zeitbuchung
Update(ctx context.Context, entry *entities.TimeEntry) functional.Result[*entities.TimeEntry]
// Delete löscht eine Zeitbuchung
Delete(ctx context.Context, companyID, id uuid.UUID) functional.Result[bool]
// GetSummary berechnet eine Zusammenfassung der Zeitbuchungen
GetSummary(ctx context.Context, companyID uuid.UUID, filter TimeEntryFilter) functional.Result[TimeEntrySummary]
}
// TimeEntrySummary enthält zusammengefasste Informationen über Zeitbuchungen
type TimeEntrySummary struct {
TotalDuration int
TotalBillableDuration int
TotalAmount float64
TotalBillableAmount float64
EntriesCount int
}

View File

@ -1,99 +0,0 @@
// application/timetracking/create_time_entry.go
package main
import (
"context"
"time"
"github.com/email/timetracker/internal/domain/entities"
"github.com/email/timetracker/internal/domain/repositories"
"github.com/email/timetracker/pkg/functional"
"github.com/email/timetracker/pkg/validator"
"github.com/google/uuid"
)
// CreateTimeEntryCommand enthält die Daten zum Erstellen einer Zeitbuchung
type CreateTimeEntryCommand struct {
UserID uuid.UUID
ProjectID uuid.UUID
ActivityID uuid.UUID
TaskID *uuid.UUID
StartTime time.Time
EndTime time.Time
Description string
BillablePercentage int
}
// CreateTimeEntryUseCase repräsentiert den Anwendungsfall zum Erstellen einer Zeitbuchung
type CreateTimeEntryUseCase struct {
timeEntryRepo repositories.TimeEntryRepository
projectRepo repositories.ProjectRepository
activityRepo repositories.ActivityRepository
userRepo repositories.UserRepository
}
// NewCreateTimeEntryUseCase erstellt eine neue Instanz des UseCase
func NewCreateTimeEntryUseCase(
timeEntryRepo repositories.TimeEntryRepository,
projectRepo repositories.ProjectRepository,
activityRepo repositories.ActivityRepository,
userRepo repositories.UserRepository,
) *CreateTimeEntryUseCase {
return &CreateTimeEntryUseCase{
timeEntryRepo: timeEntryRepo,
projectRepo: projectRepo,
activityRepo: activityRepo,
userRepo: userRepo,
}
}
// Execute führt den Anwendungsfall aus
func (uc *CreateTimeEntryUseCase) Execute(ctx context.Context, companyID uuid.UUID, cmd CreateTimeEntryCommand) functional.Result[*entities.TimeEntry] {
// Validierung
if err := validator.ValidateStruct(cmd); err != nil {
return functional.Failure[*entities.TimeEntry](err)
}
// Überprüfen, ob Projekt existiert und zum gleichen Tenant gehört
projectResult := uc.projectRepo.FindByID(ctx, companyID, cmd.ProjectID)
if projectResult.IsFailure() {
return functional.Failure[*entities.TimeEntry](projectResult.Error())
}
// Überprüfen, ob Activity existiert und zum gleichen Tenant gehört
activityResult := uc.activityRepo.FindByID(ctx, companyID, cmd.ActivityID)
if activityResult.IsFailure() {
return functional.Failure[*entities.TimeEntry](activityResult.Error())
}
activity := activityResult.Value()
// Benutzer abrufen für den Stundensatz
userResult := uc.userRepo.FindByID(ctx, companyID, cmd.UserID)
if userResult.IsFailure() {
return functional.Failure[*entities.TimeEntry](userResult.Error())
}
user := userResult.Value()
// Berechnung der Dauer in Minuten
durationMinutes := int(cmd.EndTime.Sub(cmd.StartTime).Minutes())
// TimeEntry erstellen
timeEntry := &entities.TimeEntry{
TenantEntity: entities.TenantEntity{
CompanyID: companyID,
},
UserID: cmd.UserID,
ProjectID: cmd.ProjectID,
ActivityID: cmd.ActivityID,
TaskID: cmd.TaskID,
StartTime: cmd.StartTime,
EndTime: cmd.EndTime,
DurationMinutes: durationMinutes,
Description: cmd.Description,
BillablePercentage: cmd.BillablePercentage,
BillingRate: activity.BillingRate,
}
// Speichern der TimeEntry
return uc.timeEntryRepo.Create(ctx, timeEntry)
}

View File

@ -1,104 +0,0 @@
package models
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// BaseEntity enthält gemeinsame Felder für alle Entitäten
type BaseEntity struct {
ID uuid.UUID `gorm:"type:uuid;primary_key"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `gorm:"index"`
}
// BeforeCreate setzt eine neue UUID vor dem Erstellen
func (base *BaseEntity) BeforeCreate(tx *gorm.DB) error {
if base.ID == uuid.Nil {
base.ID = uuid.New()
}
return nil
}
// TenantEntity erweitert BaseEntity um Company-ID für Multi-Tenancy
type TenantEntity struct {
BaseEntity
CompanyID uuid.UUID `gorm:"type:uuid;index:idx_tenant"`
}
// Role repräsentiert eine Benutzerrolle
type Role struct {
BaseEntity
Name string `gorm:"unique;not null"`
Description string
Permissions []Permission `gorm:"many2many:role_permissions;"`
}
// Permission repräsentiert eine einzelne Berechtigung
type Permission struct {
BaseEntity
Resource string `gorm:"not null"`
Action string `gorm:"not null"`
UniqueID string `gorm:"uniqueIndex"`
}
// User repräsentiert einen Benutzer im System
type User struct {
TenantEntity
Email string `gorm:"uniqueIndex;not null"`
FirstName string
LastName string
PasswordHash string `gorm:"not null"`
RoleID uuid.UUID `gorm:"type:uuid"`
Role Role `gorm:"foreignKey:RoleID"`
HourlyRate float64
IsActive bool `gorm:"default:true"`
}
// FullName gibt den vollständigen Namen des Benutzers zurück
func (u User) FullName() string {
return u.FirstName + " " + u.LastName
}
// TimeEntry repräsentiert eine Zeitbuchung
type TimeEntry struct {
TenantEntity
UserID uuid.UUID `gorm:"type:uuid;index:idx_user"`
User User `gorm:"foreignKey:UserID"`
ProjectID uuid.UUID `gorm:"type:uuid;index:idx_project"`
Project Project `gorm:"foreignKey:ProjectID"`
ActivityID uuid.UUID `gorm:"type:uuid"`
Activity Activity `gorm:"foreignKey:ActivityID"`
TaskID *uuid.UUID `gorm:"type:uuid;null"`
Task *Task `gorm:"foreignKey:TaskID"`
StartTime time.Time `gorm:"not null;index:idx_time_range"`
EndTime time.Time `gorm:"not null;index:idx_time_range"`
DurationMinutes int `gorm:"not null"`
Description string
BillablePercentage int `gorm:"default:100"`
BillingRate float64
}
type Project struct {
TenantEntity
Name string
}
type Activity struct {
TenantEntity
Name string
}
type Task struct {
TenantEntity
Name string
}
// CalculateBillableAmount berechnet den abrechenbaren Betrag
func (t TimeEntry) CalculateBillableAmount() float64 {
hours := float64(t.DurationMinutes) / 60.0
return hours * t.BillingRate * (float64(t.BillablePercentage) / 100.0)
}

View File

@ -1,136 +0,0 @@
// presentation/components/timeTracker/Timer/Timer.tsx
import React, { useState, useEffect } from 'react';
import { useTimeTracking } from '../../../hooks/useTimeTracking';
import { pipe, Option, fromNullable } from '../../../../utils/fp/option';
import { Button } from '../../common/Button';
import { formatDuration } from '../../../../utils/date/dateUtils';
interface TimerProps {
onComplete?: (duration: number) => void;
}
export const Timer: React.FC<TimerProps> = ({ onComplete }) => {
const [isRunning, setIsRunning] = useState(false);
const [startTime, setStartTime] = useState<Option<Date>>(Option.none());
const [elapsedTime, setElapsedTime] = useState(0);
const [selectedProject, setSelectedProject] = useState<Option<string>>(Option.none());
const [selectedActivity, setSelectedActivity] = useState<Option<string>>(Option.none());
const { lastTimeEntry, projects, activities } = useTimeTracking();
// Beim ersten Rendering die letzte Zeitbuchung laden
useEffect(() => {
pipe(
fromNullable(lastTimeEntry),
Option.map(entry => {
setSelectedProject(Option.some(entry.projectId));
setSelectedActivity(Option.some(entry.activityId));
})
);
}, [lastTimeEntry]);
// Timer-Logik
useEffect(() => {
let interval: NodeJS.Timeout | null = null;
if (isRunning) {
interval = setInterval(() => {
const now = new Date();
pipe(
startTime,
Option.map(start => {
const diff = now.getTime() - start.getTime();
setElapsedTime(Math.floor(diff / 1000));
})
);
}, 1000);
} else if (interval) {
clearInterval(interval);
}
return () => {
if (interval) clearInterval(interval);
};
}, [isRunning, startTime]);
// Timer starten
const handleStart = () => {
setStartTime(Option.some(new Date()));
setIsRunning(true);
};
// Timer stoppen
const handleStop = () => {
setIsRunning(false);
// Prüfen, ob Projekt und Aktivität ausgewählt wurden
const projectId = pipe(
selectedProject,
Option.getOrElse(() => '')
);
const activityId = pipe(
selectedActivity,
Option.getOrElse(() => '')
);
if (projectId && activityId && onComplete) {
onComplete(elapsedTime);
}
// Timer zurücksetzen
setElapsedTime(0);
setStartTime(Option.none());
};
return (
<div className="bg-white rounded-lg shadow-md p-4">
<div className="text-4xl text-center font-mono mb-4">
{formatDuration(elapsedTime)}
</div>
<div className="grid grid-cols-2 gap-4 mb-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Projekt
</label>
<select
className="w-full border border-gray-300 rounded-md px-3 py-2"
value={pipe(selectedProject, Option.getOrElse(() => ''))}
onChange={(e) => setSelectedProject(Option.some(e.target.value))}
disabled={isRunning}
>
<option value="">Projekt auswählen</option>
{projects.map((project) => (
<option key={project.id} value={project.id}>
{project.name}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Tätigkeit
</label>
<select
className="w-full border border-gray-300 rounded-md px-3 py-2"
value={pipe(selectedActivity, Option.getOrElse(() => ''))}
onChange={(e) => setSelectedActivity(Option.some(e.target.value))}
disabled={isRunning}
>
<option value="">Tätigkeit auswählen</option>
{activities.map((activity) => (
<option key={activity.id} value={activity.id}>
{activity.name}
</option>
))}
</select>
</div>
</div>
<div className="flex justify-center">
</div>
</div>
);
};