How to save a users presence in a MySQL database?

Viewed 41

I want a user to save his presence at a location. The presence can be all day or from e.g. 10am to 8pm. There is the following form:

Location:   [
             'Location A',
             'Location B',
             'Location C'
            ]
Start Date: [Y-m-d]
Start Time: [H:i] (only shown if allDay = false)
End   Date: [Y-m-d]
End   Time: [H:i] (only shown if allDay = false)
allDay:     [x]
Frequency:  [
             'once',
             'weekly',
             'every 2nd week',
             'monthly'
            ]
User id:    [hidden]

I thought about having this table:

CREATE TABLE `presences` (
  `id`          bigint unsigned NOT NULL AUTO_INCREMENT,
  `user_id`     bigint unsigned DEFAULT NULL,
  `location_id` bigint unsigned DEFAULT NULL,
  `start`       datetime NOT NULL,
  `end`         datetime NOT NULL,
  `frequency`   varchar(255) NOT NULL,
  `interval`    int NOT NULL,
  `allDay`      tinyint(1) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `presences_location_id_foreign` (`location_id`),
  KEY `presences_user_id_foreign`     (`user_id`),
  CONSTRAINT `presences_location_id_foreign` FOREIGN KEY (`location_id`) REFERENCES `locations` (`id`) ON DELETE CASCADE,
  CONSTRAINT `presences_user_id_foreign`     FOREIGN KEY (`user_id`)     REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

For the frequency I could store one of ['once','weekly','monthly'] where interval is always one, expect when weekly2 is selected, it is 2.

The issue that I have is: E.g. presence is planned from 2022-08-19 08:00 to 2022-08-21 18:00 for a calendar this would be one 58h event.

But from the users perspective this means every day between 19th and 21th from 08am to 6pm each.

And this is what I finally want to display in a calendar: For the given example I want to show 3 events (19th, 20th, 21th) starting from 8am to 6pm each, but now I need to add extra logic the data in the database and I can't just query

Give me the presence of the user at the location

I already created that logic in php:

public function spreadBetween($presence, $start, $end)
    {
        $presences = [];
        $period = CarbonPeriod::create($start->format('Y-m-d'), $end->format('Y-m-d'));
        $periodPresence = CarbonPeriod::create($presence->start->format('Y-m-d'), $presence->end->format('Y-m-d'));
        foreach ($period as $date) {
            if ($periodPresence->contains($date)) {
                $presence->start = Carbon::createFromFormat('Y-m-d H:i:s', $date->format('Y-m-d') . ' ' . $presence->start->format('H:i:s'));
                $presence->end = Carbon::createFromFormat('Y-m-d H:i:s', $date->format('Y-m-d') . ' ' . $presence->end->format('H:i:s'));
                $presences[] = $presence->replicate();
            }
        }

        return $presences;
    }

And now comes the very tricky part that lets me believe I'm not doing it right:

If you plan a meeting with a user at a location at a specific time, the user is present, but not available. This block should not be part of the final presence result.

This is why I thought it makes sense, to not split the days in to events. I need to split them further in hours:

    public function createPresenceSlots(int $uid, int $lid, string $start, string $end, $duration)
    {

        $group = uniqid();

        $start = Carbon::parse($start);
        $end = Carbon::parse($end);

        $presences = [];
        $period = CarbonPeriod::create($start, $end);
        foreach ($period as $date) {
            $intervalEnd = Carbon::createFromFormat('Y-m-d H:i:s', $date->format('Y-m-d') . ' ' . $end->format('H:i:s'));
            while ($date <= $intervalEnd) {
                $presences[] = Presence::create([
                    'user_id' => $uid,
                    'location_id' => $lid,
                    'end' => $end,
                    'allDay' => 0,
                ]);
                $date->addMinutes($duration);
            }
        }
        return $presences;
    }
}

Then I could just delete a row if the user is taken. But I would end up with a million data rows with that approach.

And now here I am, asking how to do this correctly and with a more intuitive queryable way.

0 Answers
Related