feat: redesigned DB schema with users, work_types, days, events
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"database/sql"
|
||||
"log/slog"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
@@ -16,6 +17,48 @@ type Store struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID int64
|
||||
ChatID int64
|
||||
Timezone string
|
||||
IsActive bool
|
||||
ReportEnabled bool
|
||||
ReportTime string
|
||||
LastReportDate *string
|
||||
ExportAccent string
|
||||
CreatedAt int64
|
||||
UpdatedAt int64
|
||||
}
|
||||
|
||||
type WorkType struct {
|
||||
ID int64
|
||||
Name string
|
||||
CreatedAt int64
|
||||
}
|
||||
|
||||
type Day struct {
|
||||
ID int64
|
||||
UserID int64
|
||||
Date string
|
||||
IsDayOff bool
|
||||
DayOffReason string
|
||||
CurrentWorkTypeID int64
|
||||
MinBreakThreshold int64
|
||||
CreatedAt int64
|
||||
UpdatedAt int64
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
ID int64
|
||||
UserID int64
|
||||
DayID int64
|
||||
EventType string
|
||||
WorkTypeID *int64
|
||||
OccurredAt int64
|
||||
Note string
|
||||
CreatedAt int64
|
||||
}
|
||||
|
||||
func NewStore(path string) (*Store, error) {
|
||||
db, err := sql.Open(driverName, path)
|
||||
if err != nil {
|
||||
@@ -37,6 +80,10 @@ func (s *Store) Close() error {
|
||||
return s.db.Close()
|
||||
}
|
||||
|
||||
func (s *Store) DB() *sql.DB {
|
||||
return s.db
|
||||
}
|
||||
|
||||
func execSchema(db *sql.DB, path string) error {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
@@ -46,15 +93,10 @@ func execSchema(db *sql.DB, path string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func migrate(db *sql.DB) error {
|
||||
if hasColumn(db, "events", "chat_id") {
|
||||
return nil
|
||||
}
|
||||
slog.Info("migrating database to per-chat schema")
|
||||
for _, t := range []string{"days_off", "settings", "events"} {
|
||||
db.Exec("DROP TABLE IF EXISTS " + t)
|
||||
}
|
||||
return execSchema(db, schemaPath)
|
||||
func hasTable(db *sql.DB, name string) bool {
|
||||
var exists int
|
||||
db.QueryRow("SELECT COUNT(1) FROM sqlite_master WHERE type='table' AND name=?", name).Scan(&exists)
|
||||
return exists > 0
|
||||
}
|
||||
|
||||
func hasColumn(db *sql.DB, table, column string) bool {
|
||||
@@ -78,22 +120,385 @@ func hasColumn(db *sql.DB, table, column string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Store) CreateEvent(chatID int64, eventType string, occurredAt int64, note string) error {
|
||||
func migrate(db *sql.DB) error {
|
||||
if hasTable(db, "users") {
|
||||
return nil
|
||||
}
|
||||
if hasTable(db, "events") && !hasColumn(db, "events", "user_id") {
|
||||
slog.Info("migrating from old schema to new schema")
|
||||
if err := migrateFromOldSchema(db); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrateFromOldSchema(db *sql.DB) error {
|
||||
if err := execSchema(db, schemaPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
migrateUsers(db)
|
||||
migrateDaysOff(db)
|
||||
migrateEvents(db)
|
||||
migrateSettings(db)
|
||||
|
||||
for _, t := range []string{"days_off", "settings", "events"} {
|
||||
if hasTable(db, t) {
|
||||
db.Exec("DROP TABLE IF EXISTS " + t)
|
||||
}
|
||||
}
|
||||
|
||||
slog.Info("migration complete")
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrateUsers(db *sql.DB) {
|
||||
rows, err := db.Query("SELECT DISTINCT chat_id FROM events")
|
||||
if err != nil {
|
||||
slog.Warn("migration: no events table to migrate users from")
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var chatID int64
|
||||
if err := rows.Scan(&chatID); err != nil {
|
||||
continue
|
||||
}
|
||||
db.Exec(
|
||||
"INSERT OR IGNORE INTO users (chat_id, timezone) VALUES (?, 'Asia/Tehran')",
|
||||
chatID,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func migrateDaysOff(db *sql.DB) {
|
||||
if !hasTable(db, "days_off") {
|
||||
return
|
||||
}
|
||||
rows, err := db.Query("SELECT chat_id, date, reason FROM days_off")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var chatID int64
|
||||
var date, reason string
|
||||
if err := rows.Scan(&chatID, &date, &reason); err != nil {
|
||||
continue
|
||||
}
|
||||
var userID int64
|
||||
err := db.QueryRow("SELECT id FROM users WHERE chat_id=?", chatID).Scan(&userID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
db.Exec(
|
||||
"INSERT INTO days (user_id, date, is_day_off, day_off_reason) VALUES (?, ?, 1, ?) ON CONFLICT(user_id, date) DO UPDATE SET is_day_off=1, day_off_reason=excluded.day_off_reason",
|
||||
userID, date, reason,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func migrateEvents(db *sql.DB) {
|
||||
if !hasTable(db, "events") {
|
||||
return
|
||||
}
|
||||
rows, err := db.Query("SELECT chat_id, event_type, occurred_at, note, created_at FROM events ORDER BY occurred_at ASC")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var chatID int64
|
||||
var eventType, note string
|
||||
var occurredAt, createdAt int64
|
||||
if err := rows.Scan(&chatID, &eventType, &occurredAt, ¬e, &createdAt); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var userID int64
|
||||
err := db.QueryRow("SELECT id FROM users WHERE chat_id=?", chatID).Scan(&userID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
date := time.Unix(occurredAt, 0).UTC().Format("2006-01-02")
|
||||
db.Exec("INSERT OR IGNORE INTO days (user_id, date) VALUES (?, ?)", userID, date)
|
||||
|
||||
var dayID int64
|
||||
db.QueryRow("SELECT id FROM days WHERE user_id=? AND date=?", userID, date).Scan(&dayID)
|
||||
|
||||
db.Exec(
|
||||
"INSERT INTO events (user_id, day_id, event_type, occurred_at, note, created_at) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
userID, dayID, eventType, occurredAt, note, createdAt,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func migrateSettings(db *sql.DB) {
|
||||
if !hasTable(db, "settings") {
|
||||
return
|
||||
}
|
||||
rows, err := db.Query("SELECT chat_id, key, value FROM settings")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var chatID int64
|
||||
var key, value string
|
||||
if err := rows.Scan(&chatID, &key, &value); err != nil {
|
||||
continue
|
||||
}
|
||||
if key == "remote_flag" {
|
||||
var userID int64
|
||||
err := db.QueryRow("SELECT id FROM users WHERE chat_id=?", chatID).Scan(&userID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
wt := int64(1)
|
||||
if value == "remote" {
|
||||
wt = 2
|
||||
}
|
||||
db.Exec("UPDATE days SET current_work_type_id=? WHERE user_id=? AND date=?", wt, userID, time.Now().UTC().Format("2006-01-02"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) GetOrCreateUser(chatID int64) (*User, error) {
|
||||
user, err := s.GetUserByChatID(chatID)
|
||||
if err == nil {
|
||||
return user, nil
|
||||
}
|
||||
if err != sql.ErrNoRows {
|
||||
return nil, err
|
||||
}
|
||||
_, err = s.db.Exec(
|
||||
"INSERT INTO users (chat_id) VALUES (?)",
|
||||
chatID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.GetUserByChatID(chatID)
|
||||
}
|
||||
|
||||
func (s *Store) GetUserByChatID(chatID int64) (*User, error) {
|
||||
row := s.db.QueryRow(`
|
||||
SELECT id, chat_id, timezone, is_active, report_enabled, report_time,
|
||||
last_report_date, export_accent, created_at, updated_at
|
||||
FROM users WHERE chat_id = ?
|
||||
`, chatID)
|
||||
var u User
|
||||
var lastReport sql.NullString
|
||||
err := row.Scan(
|
||||
&u.ID, &u.ChatID, &u.Timezone, &u.IsActive, &u.ReportEnabled, &u.ReportTime,
|
||||
&lastReport, &u.ExportAccent, &u.CreatedAt, &u.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lastReport.Valid {
|
||||
u.LastReportDate = &lastReport.String
|
||||
}
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateUser(user *User) error {
|
||||
_, err := s.db.Exec(
|
||||
"INSERT INTO events (chat_id, event_type, occurred_at, note) VALUES (?, ?, ?, ?)",
|
||||
chatID, eventType, occurredAt, note,
|
||||
"UPDATE users SET timezone=?, is_active=?, report_enabled=?, report_time=?, last_report_date=?, export_accent=?, updated_at=unixepoch() WHERE id=?",
|
||||
user.Timezone, user.IsActive, user.ReportEnabled, user.ReportTime, user.LastReportDate, user.ExportAccent, user.ID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) GetLastEvent(chatID int64) (*Event, error) {
|
||||
func (s *Store) MarkReportSent(userID int64, date string) error {
|
||||
_, err := s.db.Exec(
|
||||
"UPDATE users SET last_report_date=?, updated_at=unixepoch() WHERE id=?",
|
||||
date, userID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) GetAllUsers() ([]User, error) {
|
||||
rows, err := s.db.Query(`
|
||||
SELECT id, chat_id, timezone, is_active, report_enabled, report_time,
|
||||
last_report_date, export_accent, created_at, updated_at
|
||||
FROM users
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var users []User
|
||||
for rows.Next() {
|
||||
var u User
|
||||
var lastReport sql.NullString
|
||||
if err := rows.Scan(
|
||||
&u.ID, &u.ChatID, &u.Timezone, &u.IsActive, &u.ReportEnabled, &u.ReportTime,
|
||||
&lastReport, &u.ExportAccent, &u.CreatedAt, &u.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lastReport.Valid {
|
||||
u.LastReportDate = &lastReport.String
|
||||
}
|
||||
users = append(users, u)
|
||||
}
|
||||
return users, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) GetWorkTypes() ([]WorkType, error) {
|
||||
rows, err := s.db.Query("SELECT id, name, created_at FROM work_types ORDER BY id ASC")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var wts []WorkType
|
||||
for rows.Next() {
|
||||
var wt WorkType
|
||||
if err := rows.Scan(&wt.ID, &wt.Name, &wt.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wts = append(wts, wt)
|
||||
}
|
||||
return wts, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) GetWorkType(id int64) (*WorkType, error) {
|
||||
var wt WorkType
|
||||
err := s.db.QueryRow("SELECT id, name, created_at FROM work_types WHERE id=?", id).Scan(&wt.ID, &wt.Name, &wt.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &wt, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetOrCreateDay(userID int64, date string) (*Day, error) {
|
||||
day, err := s.GetDay(userID, date)
|
||||
if err == nil {
|
||||
return day, nil
|
||||
}
|
||||
if err != sql.ErrNoRows {
|
||||
return nil, err
|
||||
}
|
||||
_, err = s.db.Exec(
|
||||
"INSERT INTO days (user_id, date) VALUES (?, ?)",
|
||||
userID, date,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.GetDay(userID, date)
|
||||
}
|
||||
|
||||
func (s *Store) GetDay(userID int64, date string) (*Day, error) {
|
||||
row := s.db.QueryRow(`
|
||||
SELECT id, user_id, date, is_day_off, day_off_reason,
|
||||
current_work_type_id, min_break_threshold, created_at, updated_at
|
||||
FROM days WHERE user_id=? AND date=?
|
||||
`, userID, date)
|
||||
var d Day
|
||||
err := row.Scan(
|
||||
&d.ID, &d.UserID, &d.Date, &d.IsDayOff, &d.DayOffReason,
|
||||
&d.CurrentWorkTypeID, &d.MinBreakThreshold, &d.CreatedAt, &d.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateDay(day *Day) error {
|
||||
_, err := s.db.Exec(
|
||||
"UPDATE days SET is_day_off=?, day_off_reason=?, current_work_type_id=?, min_break_threshold=?, updated_at=unixepoch() WHERE id=?",
|
||||
day.IsDayOff, day.DayOffReason, day.CurrentWorkTypeID, day.MinBreakThreshold, day.ID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) SetDayOff(userID int64, date string, reason string) error {
|
||||
_, err := s.GetOrCreateDay(userID, date)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.db.Exec(
|
||||
"UPDATE days SET is_day_off=1, day_off_reason=?, updated_at=unixepoch() WHERE user_id=? AND date=?",
|
||||
reason, userID, date,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) RemoveDayOff(userID int64, date string) error {
|
||||
_, err := s.db.Exec(
|
||||
"UPDATE days SET is_day_off=0, day_off_reason='', updated_at=unixepoch() WHERE user_id=? AND date=?",
|
||||
userID, date,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) IsDayOff(userID int64, date string) (bool, error) {
|
||||
var isOff bool
|
||||
err := s.db.QueryRow(
|
||||
"SELECT is_day_off FROM days WHERE user_id=? AND date=?",
|
||||
userID, date,
|
||||
).Scan(&isOff)
|
||||
if err == sql.ErrNoRows {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return isOff, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetUserDaysInRange(userID int64, startDate, endDate string) ([]Day, error) {
|
||||
rows, err := s.db.Query(`
|
||||
SELECT id, user_id, date, is_day_off, day_off_reason,
|
||||
current_work_type_id, min_break_threshold, created_at, updated_at
|
||||
FROM days
|
||||
WHERE user_id=? AND date >= ? AND date <= ?
|
||||
ORDER BY date ASC
|
||||
`, userID, startDate, endDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var days []Day
|
||||
for rows.Next() {
|
||||
var d Day
|
||||
if err := rows.Scan(
|
||||
&d.ID, &d.UserID, &d.Date, &d.IsDayOff, &d.DayOffReason,
|
||||
&d.CurrentWorkTypeID, &d.MinBreakThreshold, &d.CreatedAt, &d.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
days = append(days, d)
|
||||
}
|
||||
return days, rows.Err()
|
||||
}
|
||||
|
||||
func (s *Store) CreateEvent(userID int64, dayID int64, eventType string, workTypeID *int64, occurredAt int64, note string) error {
|
||||
_, err := s.db.Exec(
|
||||
"INSERT INTO events (user_id, day_id, event_type, work_type_id, occurred_at, note) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
userID, dayID, eventType, workTypeID, occurredAt, note,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) GetLastEvent(userID int64) (*Event, error) {
|
||||
row, err := s.db.Query(`
|
||||
SELECT id, chat_id, event_type, occurred_at, note, created_at
|
||||
SELECT id, user_id, day_id, event_type, work_type_id, occurred_at, note, created_at
|
||||
FROM events
|
||||
WHERE chat_id = ?
|
||||
WHERE user_id = ?
|
||||
ORDER BY occurred_at DESC
|
||||
LIMIT 1
|
||||
`, chatID)
|
||||
`, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -102,58 +507,24 @@ func (s *Store) GetLastEvent(chatID int64) (*Event, error) {
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
var e Event
|
||||
if err := row.Scan(&e.ID, &e.ChatID, &e.EventType, &e.OccurredAt, &e.Note, &e.CreatedAt); err != nil {
|
||||
var wt sql.NullInt64
|
||||
if err := row.Scan(&e.ID, &e.UserID, &e.DayID, &e.EventType, &wt, &e.OccurredAt, &e.Note, &e.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if wt.Valid {
|
||||
e.WorkTypeID = &wt.Int64
|
||||
}
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetSetting(chatID int64, key string) (string, error) {
|
||||
var value string
|
||||
err := s.db.QueryRow("SELECT value FROM settings WHERE chat_id = ? AND key = ?", chatID, key).Scan(&value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (s *Store) SetSetting(chatID int64, key, value string) error {
|
||||
_, err := s.db.Exec(`
|
||||
INSERT INTO settings (chat_id, key, value) VALUES (?, ?, ?)
|
||||
ON CONFLICT(chat_id, key) DO UPDATE SET value=excluded.value
|
||||
`, chatID, key, value)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) SetDayOff(chatID int64, date, reason string) error {
|
||||
_, err := s.db.Exec(`
|
||||
INSERT INTO days_off (chat_id, date, reason) VALUES (?, ?, ?)
|
||||
ON CONFLICT(chat_id, date) DO UPDATE SET reason=excluded.reason
|
||||
`, chatID, date, reason)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) RemoveDayOff(chatID int64, date string) error {
|
||||
_, err := s.db.Exec("DELETE FROM days_off WHERE chat_id = ? AND date = ?", chatID, date)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) IsDayOff(chatID int64, date string) (bool, error) {
|
||||
var exists int
|
||||
err := s.db.QueryRow("SELECT COUNT(1) FROM days_off WHERE chat_id = ? AND date = ?", chatID, date).Scan(&exists)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return exists > 0, nil
|
||||
}
|
||||
|
||||
func (s *Store) EventsForDay(chatID int64, day string) ([]Event, error) {
|
||||
func (s *Store) EventsForDayByDate(userID int64, date string) ([]Event, error) {
|
||||
rows, err := s.db.Query(`
|
||||
SELECT id, chat_id, event_type, occurred_at, note, created_at
|
||||
FROM events
|
||||
WHERE chat_id = ? AND strftime('%Y-%m-%d', occurred_at, 'unixepoch') = ?
|
||||
ORDER BY occurred_at ASC
|
||||
`, chatID, day)
|
||||
SELECT e.id, e.user_id, e.day_id, e.event_type, e.work_type_id, e.occurred_at, e.note, e.created_at
|
||||
FROM events e
|
||||
JOIN days d ON d.id = e.day_id
|
||||
WHERE d.user_id = ? AND d.date = ?
|
||||
ORDER BY e.occurred_at ASC
|
||||
`, userID, date)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -161,19 +532,40 @@ func (s *Store) EventsForDay(chatID int64, day string) ([]Event, error) {
|
||||
var events []Event
|
||||
for rows.Next() {
|
||||
var e Event
|
||||
if err := rows.Scan(&e.ID, &e.ChatID, &e.EventType, &e.OccurredAt, &e.Note, &e.CreatedAt); err != nil {
|
||||
var wt sql.NullInt64
|
||||
if err := rows.Scan(&e.ID, &e.UserID, &e.DayID, &e.EventType, &wt, &e.OccurredAt, &e.Note, &e.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if wt.Valid {
|
||||
e.WorkTypeID = &wt.Int64
|
||||
}
|
||||
events = append(events, e)
|
||||
}
|
||||
return events, rows.Err()
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
ID int64
|
||||
ChatID int64
|
||||
EventType string
|
||||
OccurredAt int64
|
||||
Note string
|
||||
CreatedAt int64
|
||||
func (s *Store) EventsForDayByDayID(dayID int64) ([]Event, error) {
|
||||
rows, err := s.db.Query(`
|
||||
SELECT id, user_id, day_id, event_type, work_type_id, occurred_at, note, created_at
|
||||
FROM events
|
||||
WHERE day_id = ?
|
||||
ORDER BY occurred_at ASC
|
||||
`, dayID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var events []Event
|
||||
for rows.Next() {
|
||||
var e Event
|
||||
var wt sql.NullInt64
|
||||
if err := rows.Scan(&e.ID, &e.UserID, &e.DayID, &e.EventType, &wt, &e.OccurredAt, &e.Note, &e.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if wt.Valid {
|
||||
e.WorkTypeID = &wt.Int64
|
||||
}
|
||||
events = append(events, e)
|
||||
}
|
||||
return events, rows.Err()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user