table())->where('visitor_token', $token)->first(); } public function findById(string $id): ?\stdClass { return DB::table($this->table())->where('id', $id)->first(); } /** * Find-or-create a visitor by token, updating last_seen_at on every call. * Returns the visitor id. */ public function upsert(string $token, ?string $memberId): string { $now = Carbon::now()->toDateTimeString(); $existing = $this->findByToken($token); if ($existing) { $patch = ['last_seen_at' => $now, 'updated_at' => $now]; if ($memberId !== null && $existing->member_id === null) { $patch['member_id'] = $memberId; } DB::table($this->table())->where('id', $existing->id)->update($patch); return $existing->id; } $id = $this->ids->generate(); DB::table($this->table())->insert([ 'id' => $id, 'visitor_token' => $token, 'member_id' => $memberId, 'first_seen_at' => $now, 'last_seen_at' => $now, 'created_at' => $now, 'updated_at' => $now, ]); return $id; } /** * Link an anonymous visitor to a known member after login. * Only writes when member_id is not already set to prevent duplicate stitches. */ public function stitch(string $visitorId, string $memberId): void { DB::table($this->table()) ->where('id', $visitorId) ->whereNull('member_id') ->update([ 'member_id' => $memberId, 'updated_at' => Carbon::now()->toDateTimeString(), ]); } }