@@ -22,6 +22,13 @@ import type {
2222import { OURMOJI_ENTRY_TYPE } from "~/utils/ourmoji/constants" ;
2323import { ourmojiChildLogger } from "./logger" ;
2424
25+ /**
26+ * Default category/subcategory slugs for Ourmoji entries, matching the
27+ * in-app "Moments → Magic" bucket in `categoryDefaults.ts`.
28+ */
29+ const DEFAULT_OURMOJI_CATEGORY = "moments" ;
30+ const DEFAULT_OURMOJI_SUBCATEGORY = "magic" ;
31+
2532const logger = ourmojiChildLogger ( "service:daily" ) ;
2633
2734export interface DailyIngestInput {
@@ -36,6 +43,11 @@ export interface DailyIngestInput {
3643 timezone : string ;
3744 /** Source label — `manual` for in-app submissions, `api` for OpenClaw. */
3845 source ?: "manual" | "api" ;
46+ /** ISO-8601 datetime when the reading was generated. Defaults to NOW. */
47+ timestamp ?: string | null ;
48+ /** Optional category hierarchy labels, matching `entries.category`. */
49+ category ?: string | null ;
50+ subcategory ?: string | null ;
3951}
4052
4153/**
@@ -72,6 +84,11 @@ export async function upsertDailyOurmoji(
7284 timezone : input . timezone ,
7385 source : input . source ?? "api" ,
7486 updatedAt : sql `(datetime('now'))` ,
87+ ...( input . timestamp ? { timestamp : input . timestamp } : { } ) ,
88+ ...( input . category !== undefined ? { category : input . category } : { } ) ,
89+ ...( input . subcategory !== undefined
90+ ? { subcategory : input . subcategory }
91+ : { } ) ,
7592 } )
7693 . where ( eq ( entries . id , existing . id ) )
7794 . returning ( ) ;
@@ -90,10 +107,12 @@ export async function upsertDailyOurmoji(
90107 name : "Ourmoji" ,
91108 emoji : input . emoji ,
92109 notes : input . reflection ,
93- timestamp : ` ${ input . date } T00:00:00Z` ,
110+ timestamp : input . timestamp ?? new Date ( ) . toISOString ( ) ,
94111 timezone : input . timezone ,
95112 data : data as unknown as Record < string , unknown > ,
96113 source : input . source ?? "api" ,
114+ category : input . category ?? DEFAULT_OURMOJI_CATEGORY ,
115+ subcategory : input . subcategory ?? DEFAULT_OURMOJI_SUBCATEGORY ,
97116 } ;
98117
99118 const [ created ] = await db . insert ( entries ) . values ( newRow ) . returning ( ) ;
@@ -104,15 +123,20 @@ export async function upsertDailyOurmoji(
104123 * Look up the Ourmoji entry for `userId` on `date` (YYYY-MM-DD), if any.
105124 *
106125 * Implementation note: the per-day uniqueness key lives in `data.date`,
107- * not in a top-level column. We narrow with a timestamp range first
126+ * not in a top-level column. We narrow with a ±1-day timestamp range
108127 * (cheap, indexed) and then filter rows whose JSON `date` matches.
128+ * The widened window accommodates timezone offsets: a local-date entry
129+ * stored with a UTC timestamp may fall into the prior or next UTC day.
109130 */
110131export async function findDailyEntry (
111132 userId : string ,
112133 date : string ,
113134) : Promise < Entry | undefined > {
114- const dayStart = `${ date } T00:00:00Z` ;
115- const dayEnd = `${ date } T23:59:59Z` ;
135+ const windowStart = new Date ( `${ date } T00:00:00Z` ) ;
136+ windowStart . setUTCDate ( windowStart . getUTCDate ( ) - 1 ) ;
137+ const windowEnd = new Date ( `${ date } T23:59:59Z` ) ;
138+ windowEnd . setUTCDate ( windowEnd . getUTCDate ( ) + 1 ) ;
139+
116140 const candidates = await db
117141 . select ( )
118142 . from ( entries )
@@ -121,8 +145,8 @@ export async function findDailyEntry(
121145 eq ( entries . userId , userId ) ,
122146 eq ( entries . type , OURMOJI_ENTRY_TYPE ) ,
123147 isNull ( entries . deletedAt ) ,
124- gte ( entries . timestamp , dayStart ) ,
125- lte ( entries . timestamp , dayEnd ) ,
148+ gte ( entries . timestamp , windowStart . toISOString ( ) ) ,
149+ lte ( entries . timestamp , windowEnd . toISOString ( ) ) ,
126150 ) ,
127151 ) ;
128152 return candidates . find ( ( row ) => {
@@ -175,5 +199,7 @@ function rowToDTO(row: Entry): OurmojiDailyCardDTO {
175199 wheelCategory : data . wheelCategory ?? null ,
176200 timestamp : row . timestamp ,
177201 timezone : row . timezone ,
202+ category : row . category ?? null ,
203+ subcategory : row . subcategory ?? null ,
178204 } ;
179205}
0 commit comments