dogy_backend_api/service/assistant/daily_challenges/
store.rs1use super::Error as DailyChallengeError;
2use crate::Error::DailyChallenge as Error;
3use crate::Result;
4use chrono::NaiveDate;
5use sqlx::{query, query_as, Executor, Postgres, Transaction};
6use uuid::Uuid;
7
8pub async fn retrieve_timezone_from_user<'e, E>(conn: E, user_id: Uuid) -> Result<String>
10where
11 E: Executor<'e, Database = Postgres>,
12{
13 let timezone: (String,) = query_as("SELECT timezone FROM users WHERE id = $1;")
14 .bind(user_id)
15 .fetch_one(conn)
16 .await
17 .map_err(super::Error::from)?;
18
19 if timezone.0.is_empty() {
20 return Err(Error(DailyChallengeError::MissingTimezoneForUser));
21 }
22
23 Ok(timezone.0)
24}
25
26pub async fn set_local_timezone(txn: &mut Transaction<'_, Postgres>, timezone: &str) -> Result<()> {
27 query(format!("SET LOCAL TIME ZONE '{}';", timezone).as_str())
29 .execute(&mut **txn) .await
31 .map_err(super::Error::from)?;
32
33 Ok(())
34}
35
36pub async fn verify_daily_challenge_existence(
37 txn: &mut Transaction<'_, Postgres>,
38 user_id: Uuid,
39 timezone: &str,
40) -> Result<()> {
41 set_local_timezone(txn, timezone).await?;
42
43 let challenge_id: Option<(Uuid,)> = query_as(
44 r#"SELECT id FROM user_daily_challenges
45 WHERE user_id = $1 AND created_at::date = CURRENT_DATE;"#,
46 )
47 .bind(user_id)
48 .fetch_optional(&mut **txn)
49 .await
50 .map_err(super::Error::from)?;
51
52 match challenge_id {
53 Some(id) => Err(Error(DailyChallengeError::ChallengeAlreadyCompleted {
54 challenge_id: id.0,
55 })),
56 None => Ok(()),
57 }
58}
59
60pub async fn retrieve_past_challenges<'e, E>(conn: E, user_id: Uuid) -> Result<Vec<String>>
61where
62 E: Executor<'e, Database = Postgres>,
63{
64 let past_challenges: Vec<(String,)> = query_as(
65 r#"
66 SELECT challenge
67 FROM user_daily_challenges
68 WHERE user_id = $1
69 ORDER BY created_at DESC
70 LIMIT 7;
71 "#,
72 )
73 .bind(user_id)
74 .fetch_all(conn)
75 .await
76 .map_err(super::Error::from)?;
77
78 Ok(past_challenges.into_iter().map(|(c,)| c).collect())
79}
80
81pub async fn retrieve_daily_challenge_streaks(
82 txn: &mut Transaction<'_, Postgres>,
83 user_id: Uuid,
84 timezone: &str,
85) -> Result<Vec<NaiveDate>> {
86 set_local_timezone(txn, timezone).await?;
87
88 let streaks: Vec<(NaiveDate,)> = query_as(
89 r#"
90 SELECT created_at::date AS challenge_day
91 FROM user_daily_challenges
92 WHERE user_id = $1
93 ORDER BY challenge_day;
94 "#,
95 )
96 .bind(user_id)
97 .fetch_all(&mut **txn)
98 .await
99 .map_err(super::Error::from)?;
100
101 let dates: Vec<NaiveDate> = streaks.into_iter().map(|(d,)| d).collect();
102
103 Ok(dates)
104}
105
106pub async fn save_daily_challenge(
111 txn: &mut Transaction<'_, Postgres>,
112 user_id: Uuid,
113 timezone: String,
114 challenge: &str,
115) -> Result<Uuid> {
116 set_local_timezone(txn, &timezone).await?;
118
119 let challenge_id: (Uuid,) = query_as(
120 r#"
121 INSERT INTO user_daily_challenges (user_id, challenge)
122 VALUES ($1, $2)
123 RETURNING id;
124 "#,
125 )
126 .bind(user_id)
127 .bind(challenge)
128 .fetch_one(&mut **txn)
129 .await
130 .map_err(super::Error::from)?;
131
132 Ok(challenge_id.0)
133}
134
135pub static DAILY_CHALLENGE_PROMPT: &str = r#"
137Create daily challenges and dog facts for dog owners. Challenges focus on activities such as learning a new trick, discovering a food recipe, or understanding dog behaviors.
138
139# Steps
1401. Identify Challenge Type: Determine whether the daily challenge will be a trick, recipe, behavior insight, or dog fact.
1412. Craft a Challenge or Fact: Depending on the type, create a specific, actionable challenge or provide a fascinating, relevant dog fact.
142 - For Tricks: Describe a step-by-step process to teach a new trick to a dog.
143 - For Recipes: Offer a simple recipe that is healthy and suitable for dogs.
144 - For Behaviors: Provide an informative piece about a specific dog behavior and how to positively address or encourage it.
145 - For Dog Facts: Present a short, interesting fact about dogs that can intrigue or educate dog owners.
1463. Ensure Variety: Rotate through different challenge types and facts to keep the daily content varied and engaging.
147
148# Output Format
149Each challenge or fact should be presented in a brief paragraph, clearly stating the type and giving detailed instructions, information, or an intriguing fact relevant to the challenge.
150
151## Examples
152
153### Example 1 (Trick)
154Teach your dog the 'Shake Hands' trick. Start by getting them to sit, then hold a treat in your hand close to their nose. Tap their paw gently while saying 'shake.' When they lift their paw, praise them and give them the treat. Practice this a few times for them to learn.
155
156### Example 2 (Recipe)
157Create a simple dog treat using peanut butter and pumpkin puree. Mix 1 cup of pumpkin puree with 1/4 cup of natural peanut butter. Roll the mixture into small balls and place them on a baking sheet. Freeze for a quick and easy dog snack.
158
159### Example 3 (Behavior)
160Understand your dog's wagging tail. A wagging tail can indicate happiness, but the speed and direction of the wag can signify other emotions like anxiety or excitement. Observe your dog's wagging patterns to better understand their feelings.
161
162### Example 4 (Fact)
163Dogs have three eyelids, an upper lid, a lower lid, and a third lid, known as a nictitating membrane or haw, which helps keep the eye moist and protected.
164
165# Notes
166- Customize challenges based on common dog breeds and their attributes for increased relevance.
167- Pay attention to seasonal changes, ensuring that challenges are practical and safe for the current weather.
168- Ensure recipes use dog-safe ingredients only.
169- Dog facts should aim to be surprising or educational.
170- Only output one challenge.
171- Only output the challenge or fact in paragraph form.
172- Do not include any additional text or explanations and do not exceed 100 words.
173"#;
174
175pub static PET_INFO_PROMPT: &str = r#"
176Here are the details about my pet. Use this information to create a personalized daily challenge or
177dog fact for me."#;
178
179pub static EXCLUDE_PAST_CHALLENGES_PROMPT: &str = r#"
180Exclude generating these past challenges. Be sure to generate a unique challenge for today.
181Here are the past challenges I have received: "#;