feat: initial daily-timer implementation

This commit is contained in:
Mikhail Kiselev
2026-02-08 05:17:37 +03:00
parent 537f72eb51
commit ef23291bdd
37 changed files with 7779 additions and 0 deletions

72
internal/models/models.go Normal file
View File

@@ -0,0 +1,72 @@
package models
import (
"time"
)
type Participant struct {
ID uint `json:"id" gorm:"primaryKey"`
Name string `json:"name" gorm:"not null"`
Email string `json:"email,omitempty"`
TimeLimit int `json:"timeLimit" gorm:"default:120"` // seconds
Order int `json:"order" gorm:"default:0"`
Active bool `json:"active" gorm:"default:true"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type Meeting struct {
ID uint `json:"id" gorm:"primaryKey"`
Name string `json:"name" gorm:"not null;default:Daily Standup"`
TimeLimit int `json:"timeLimit" gorm:"default:3600"` // total meeting limit in seconds (1 hour)
Sessions []MeetingSession `json:"sessions,omitempty" gorm:"foreignKey:MeetingID"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type MeetingSession struct {
ID uint `json:"id" gorm:"primaryKey"`
MeetingID uint `json:"meetingId" gorm:"not null"`
StartedAt time.Time `json:"startedAt"`
EndedAt *time.Time `json:"endedAt,omitempty"`
TotalDuration int `json:"totalDuration"` // seconds
Completed bool `json:"completed" gorm:"default:false"`
ParticipantLogs []ParticipantLog `json:"participantLogs,omitempty" gorm:"foreignKey:SessionID"`
Attendance []SessionAttendance `json:"attendance,omitempty" gorm:"foreignKey:SessionID"`
}
type ParticipantLog struct {
ID uint `json:"id" gorm:"primaryKey"`
SessionID uint `json:"sessionId" gorm:"not null"`
ParticipantID uint `json:"participantId" gorm:"not null"`
Participant Participant `json:"participant,omitempty" gorm:"foreignKey:ParticipantID"`
StartedAt time.Time `json:"startedAt"`
EndedAt *time.Time `json:"endedAt,omitempty"`
Duration int `json:"duration"` // seconds
Skipped bool `json:"skipped" gorm:"default:false"`
Overtime bool `json:"overtime" gorm:"default:false"`
Order int `json:"order"` // speaking order in session
}
type SessionAttendance struct {
ID uint `json:"id" gorm:"primaryKey"`
SessionID uint `json:"sessionId" gorm:"not null"`
ParticipantID uint `json:"participantId" gorm:"not null"`
Participant Participant `json:"participant,omitempty" gorm:"foreignKey:ParticipantID"`
Present bool `json:"present" gorm:"default:true"`
JoinedLate bool `json:"joinedLate" gorm:"default:false"`
}
type Settings struct {
ID uint `json:"id" gorm:"primaryKey"`
DefaultParticipantTime int `json:"defaultParticipantTime" gorm:"default:120"` // seconds
DefaultMeetingTime int `json:"defaultMeetingTime" gorm:"default:900"` // seconds
SoundEnabled bool `json:"soundEnabled" gorm:"default:true"`
SoundWarning string `json:"soundWarning" gorm:"default:warning.mp3"`
SoundTimeUp string `json:"soundTimeUp" gorm:"default:timeup.mp3"`
SoundMeetingEnd string `json:"soundMeetingEnd" gorm:"default:meeting_end.mp3"`
WarningThreshold int `json:"warningThreshold" gorm:"default:30"` // seconds before time up
Theme string `json:"theme" gorm:"default:dark"`
WindowWidth int `json:"windowWidth" gorm:"default:800"` // minimum 480
WindowFullHeight bool `json:"windowFullHeight" gorm:"default:true"` // use full screen height
}

98
internal/models/types.go Normal file
View File

@@ -0,0 +1,98 @@
package models
type SpeakerStatus string
const (
SpeakerStatusPending SpeakerStatus = "pending"
SpeakerStatusSpeaking SpeakerStatus = "speaking"
SpeakerStatusDone SpeakerStatus = "done"
SpeakerStatusSkipped SpeakerStatus = "skipped"
)
type TimerState struct {
Running bool `json:"running"`
Paused bool `json:"paused"`
CurrentSpeakerID uint `json:"currentSpeakerId"`
CurrentSpeaker string `json:"currentSpeaker"`
SpeakerElapsed int `json:"speakerElapsed"` // seconds
SpeakerLimit int `json:"speakerLimit"` // seconds
MeetingElapsed int `json:"meetingElapsed"` // seconds
MeetingLimit int `json:"meetingLimit"` // seconds
SpeakerOvertime bool `json:"speakerOvertime"`
MeetingOvertime bool `json:"meetingOvertime"`
Warning bool `json:"warning"`
WarningSeconds int `json:"warningSeconds"` // seconds before end for warning
TotalSpeakersTime int `json:"totalSpeakersTime"` // sum of all speaker limits
SpeakingOrder int `json:"speakingOrder"`
TotalSpeakers int `json:"totalSpeakers"`
RemainingQueue []QueuedSpeaker `json:"remainingQueue"`
AllSpeakers []SpeakerInfo `json:"allSpeakers"`
}
type SpeakerInfo struct {
ID uint `json:"id"`
Name string `json:"name"`
TimeLimit int `json:"timeLimit"`
Order int `json:"order"`
Status SpeakerStatus `json:"status"`
}
type QueuedSpeaker struct {
ID uint `json:"id"`
Name string `json:"name"`
TimeLimit int `json:"timeLimit"`
Order int `json:"order"`
}
type SessionStats struct {
SessionID uint `json:"sessionId"`
Date string `json:"date"`
TotalDuration int `json:"totalDuration"`
MeetingLimit int `json:"meetingLimit"`
Overtime bool `json:"overtime"`
ParticipantCount int `json:"participantCount"`
PresentCount int `json:"presentCount"`
AbsentCount int `json:"absentCount"`
OvertimeCount int `json:"overtimeCount"`
SkippedCount int `json:"skippedCount"`
ParticipantStats []ParticipantStats `json:"participantStats"`
}
type ParticipantStats struct {
ParticipantID uint `json:"participantId"`
Name string `json:"name"`
Duration int `json:"duration"`
TimeLimit int `json:"timeLimit"`
Overtime bool `json:"overtime"`
Skipped bool `json:"skipped"`
Present bool `json:"present"`
SpeakingOrder int `json:"speakingOrder"`
}
type AggregatedStats struct {
TotalSessions int `json:"totalSessions"`
TotalMeetingTime int `json:"totalMeetingTime"`
AverageMeetingTime float64 `json:"averageMeetingTime"`
OvertimeSessions int `json:"overtimeSessions"`
OvertimePercentage float64 `json:"overtimePercentage"`
AverageAttendance float64 `json:"averageAttendance"`
ParticipantBreakdown []ParticipantBreakdown `json:"participantBreakdown"`
}
type ParticipantBreakdown struct {
ParticipantID uint `json:"participantId"`
Name string `json:"name"`
SessionsAttended int `json:"sessionsAttended"`
TotalSpeakingTime int `json:"totalSpeakingTime"`
AverageSpeakingTime float64 `json:"averageSpeakingTime"`
OvertimeCount int `json:"overtimeCount"`
SkipCount int `json:"skipCount"`
AttendanceRate float64 `json:"attendanceRate"`
}
type ExportData struct {
ExportedAt string `json:"exportedAt"`
Participants []Participant `json:"participants"`
Sessions []SessionStats `json:"sessions"`
Statistics AggregatedStats `json:"statistics"`
}