diff --git a/src/lib/db/DatabaseManager.ts b/src/lib/db/DatabaseManager.ts index ba3822ff..a6425856 100644 --- a/src/lib/db/DatabaseManager.ts +++ b/src/lib/db/DatabaseManager.ts @@ -2,67 +2,19 @@ import { addRxPlugin } from "rxdb"; import { RxDBDevModePlugin } from "rxdb/plugins/dev-mode"; import { getRxStorageDexie } from "rxdb/plugins/storage-dexie"; import { createRxDatabase, RxDatabase } from "rxdb"; +import { v4 as uuidv4 } from "uuid"; + import toolkitItemSchema from "./schemas/toolkitItemSchema.json"; import moodRecordSchema from "./schemas/moodRecordSchema.json"; import categoriesSchema from "./schemas/categoriesSchema.json"; -import { v4 as uuidv4 } from "uuid"; +import needsCategoriesSchema from "./schemas/categoriesSchema.json"; +import needsSchema from "./schemas/categoriesSchema.json"; +import nextActionsSchema from "./schemas/categoriesSchema.json"; -addRxPlugin(RxDBDevModePlugin); +import { categories, toolkit } from "./seed/toolkit"; +import { needsCategories, needs, nextActions } from "./seed/needs"; -const seedData = { - categories: [ - { id: uuidv4(), name: "Replace", timestamp: new Date().toISOString() }, - { id: uuidv4(), name: "Barrier", timestamp: new Date().toISOString() }, - { id: uuidv4(), name: "Distract", timestamp: new Date().toISOString() }, - { - id: uuidv4(), - name: "Change Status", - timestamp: new Date().toISOString(), - }, - ], - toolkit: [ - { - id: uuidv4(), - name: "Listen to my favourite music", - categories: ["Replace", "Barrier"], - checked: false, - infoUrl: "https://google.com/music", - imageUrl: - "https://daily.jstor.org/wp-content/uploads/2023/01/good_times_with_bad_music_1050x700.jpg", - timestamp: new Date().toISOString(), - }, - { - id: uuidv4(), - name: "Breathing exercises", - categories: ["Distract"], - checked: false, - infoUrl: "https://www.youtube.com/watch?v=DbDoBzGY3vo", - imageUrl: - "https://www.bhf.org.uk/-/media/images/information-support/heart-matters/2023/december/wellbeing/deep-breathing-620x400.png?h=400&w=620&rev=4506ebd34dab4476b56c225b6ff3ad60&hash=B3CFFEEE704E4432D101432CEE8B2766", - timestamp: new Date().toISOString(), - }, - { - id: uuidv4(), - name: "Call a friend", - categories: ["Distract", "Change status"], - checked: false, - infoUrl: "https://example.com/call", - imageUrl: - "https://t4.ftcdn.net/jpg/04/63/63/59/360_F_463635935_IweuYhCqZRtHp3SLguQL8svOVroVXvvZ.jpg", - timestamp: new Date().toISOString(), - }, - { - id: uuidv4(), - name: "Drink water", - categories: ["Distract", "Change status"], - checked: false, - infoUrl: "https://example.com/call", - imageUrl: - "https://content.health.harvard.edu/wp-content/uploads/2023/07/b8a1309a-ba53-48c7-bca3-9c36aab2338a.jpg", - timestamp: new Date().toISOString(), - }, - ], -}; +addRxPlugin(RxDBDevModePlugin); let dbInstance: RxDatabase | null = null; @@ -87,7 +39,14 @@ class DatabaseManager { ignoreDuplicate: true, }); - const requiredCollections = ["categories", "mood_records", "toolkit_items"]; + const requiredCollections = [ + "categories", + "mood_records", + "toolkit_items", + "needs_categories", + "needs", + "next_actions", + ]; const existingCollections = Object.keys(dbInstance.collections); for (const collection of requiredCollections) { @@ -99,7 +58,7 @@ class DatabaseManager { } } - console.log("Database initialized."); + console.log("Database initialised."); await this.seedDatabase(); return dbInstance; } @@ -109,42 +68,33 @@ class DatabaseManager { dbInstance = await this.createDatabase(); } if (!dbInstance) { - throw new Error("Failed to initialize the database."); + throw new Error("Failed to initialise the database."); } return dbInstance; } private async seedDatabase() { - console.log("Seeding database..."); try { - await this.seedCategories(); - await this.seedToolkitItems(); + await this.seed("categories", categories); + await this.seed("toolkit_items", toolkit); + await this.seed("needs_categories", needsCategories); + await this.seed("needs", needs); + await this.seed("next_actions", nextActions); } catch (error) { console.error("Error during database seeding:", error); } } - private async seedCategories() { - if (!dbInstance) throw new Error("Database not initialized."); - if (!dbInstance.collections.categories) { - throw new Error("Categories collection is missing."); - } - const existingDocs = await dbInstance.categories.find().exec(); - if (existingDocs.length === 0) { - console.log("Seeding categories..."); - await dbInstance.categories.bulkInsert(seedData.categories); - } - } + private async seed(collectionName: string, data: T[]) { + if (!dbInstance) throw new Error("Database not initialised."); + const collection = dbInstance.collections[collectionName]; + if (!collection) + throw new Error(`${collectionName} collection is missing.`); - private async seedToolkitItems() { - if (!dbInstance) throw new Error("Database not initialized."); - if (!dbInstance.collections.toolkit_items) { - throw new Error("Toolkit items collection is missing."); - } - const existingDocs = await dbInstance.toolkit_items.find().exec(); + const existingDocs = await collection.find().exec(); if (existingDocs.length === 0) { - console.log("Seeding toolkit items..."); - await dbInstance.toolkit_items.bulkInsert(seedData.toolkit); + console.log(`Seeding ${collectionName}...`); + await collection.bulkInsert(data); } } @@ -156,6 +106,12 @@ class DatabaseManager { return { schema: moodRecordSchema }; case "toolkit_items": return { schema: toolkitItemSchema }; + case "needs_categories": + return { schema: needsCategoriesSchema }; + case "needs": + return { schema: needsSchema }; + case "next_actions": + return { schema: nextActionsSchema }; default: throw new Error(`Unknown collection: ${collectionName}`); } diff --git a/src/lib/db/schemas/categoriesSchema.json b/src/lib/db/schemas/categoriesSchema.json index 3bd9a27b..dd3f48f1 100644 --- a/src/lib/db/schemas/categoriesSchema.json +++ b/src/lib/db/schemas/categoriesSchema.json @@ -8,7 +8,8 @@ "maxLength": 100 }, "name": { - "type": "string" + "type": "string", + "maxLength": 255 }, "timestamp": { "type": "string", @@ -16,4 +17,4 @@ } }, "required": ["id", "name", "timestamp"] -} \ No newline at end of file +} diff --git a/src/lib/db/schemas/needsCategoriesSchema.json b/src/lib/db/schemas/needsCategoriesSchema.json new file mode 100644 index 00000000..77d2a2ab --- /dev/null +++ b/src/lib/db/schemas/needsCategoriesSchema.json @@ -0,0 +1,20 @@ +{ + "version": 0, + "primaryKey": "id", + "type": "object", + "properties": { + "id": { + "type": "string", + "maxLength": 100 + }, + "name": { + "type": "string", + "maxLength": 100 + }, + "timestamp": { + "type": "string", + "format": "date-time" + } + }, + "required": ["id", "name", "timestamp"] +} diff --git a/src/lib/db/schemas/needsSchema.json b/src/lib/db/schemas/needsSchema.json new file mode 100644 index 00000000..96e2b2ec --- /dev/null +++ b/src/lib/db/schemas/needsSchema.json @@ -0,0 +1,43 @@ +{ + "version": 0, + "primaryKey": "id", + "type": "object", + "properties": { + "id": { + "type": "string", + "maxLength": 100 + }, + "name": { + "type": "string", + "maxLength": 100 + }, + "selectedTimestamps": { + "type": "array", + "items": { + "type": "string", + "format": "date-time" + } + }, + "selectedExpiry": { + "type": "string", + "format": "date-time" + }, + "category": { + "type": "string", + "ref": "needs_categories", + "maxLength": 100 + }, + "timestamp": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "name", + "category", + "timestamp", + "selectedTimestamps", + "selectedExpiry" + ] +} diff --git a/src/lib/db/schemas/nextActionsSchema.json b/src/lib/db/schemas/nextActionsSchema.json new file mode 100644 index 00000000..95946ae9 --- /dev/null +++ b/src/lib/db/schemas/nextActionsSchema.json @@ -0,0 +1,43 @@ +{ + "version": 0, + "primaryKey": "id", + "type": "object", + "properties": { + "id": { + "type": "string", + "maxLength": 100 + }, + "name": { + "type": "string", + "maxLength": 255 + }, + "need": { + "type": "string", + "ref": "needs", + "maxLength": 100 + }, + "selectedTimestamps": { + "type": "array", + "items": { + "type": "string", + "format": "date-time" + } + }, + "selectedExpiry": { + "type": "string", + "format": "date-time" + }, + "timestamp": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "name", + "need", + "timestamp", + "selectedTimestamps", + "selectedExpiry" + ] +} diff --git a/src/lib/db/schemas/toolkitItemSchema.json b/src/lib/db/schemas/toolkitItemSchema.json index 6db9ca3b..0b4dcf5a 100644 --- a/src/lib/db/schemas/toolkitItemSchema.json +++ b/src/lib/db/schemas/toolkitItemSchema.json @@ -7,7 +7,10 @@ "type": "string", "maxLength": 100 }, - "name": { "type": "string" }, + "name": { + "type": "string", + "maxLength": 255 + }, "categories": { "type": "array", "items": { @@ -24,11 +27,5 @@ "format": "date-time" } }, - "required": [ - "id", - "name", - "categories", - "checked", - "timestamp" - ] -} \ No newline at end of file + "required": ["id", "name", "categories", "checked", "timestamp"] +} diff --git a/src/lib/db/seed/needs.ts b/src/lib/db/seed/needs.ts new file mode 100644 index 00000000..9cc5e573 --- /dev/null +++ b/src/lib/db/seed/needs.ts @@ -0,0 +1,154 @@ +import { v4 as uuidv4 } from "uuid"; + +const needsList = { + physicalNurturance: [ + "Air", + "Food", + "Water", + "Movement", + "Exercise", + "Protection (insects, bacteria, virus, predators)", + "Rest", + "Sexual expression", + "Shelter", + "Touch", + ], + play: ["Play", "Laughter"], + interdependence: [ + "Acceptance", + "Appreciation", + "Closeness", + "Community", + "Consideration", + "Contribution to the enrichment of life", + "Emotional safety", + "Empathy", + "Honesty", + "Love", + "Reassurance", + "Respect", + "Support", + "Trust", + "Understanding", + "Warmth", + ], + celebration: [ + "Celebration of life fulfilled", + "Celebration of dreams fulfilled", + "Mourning of dreams unfulfilled", + "Mourning losses of loved ones", + ], + harmony: ["Peace", "Order", "Harmony", "Beauty", "Inspiration"], + integrity: ["Authenticity", "Creativity", "Meaning", "Self worth"], + autonomy: [ + "Choose dreams", + "Choose goals", + "Choose values", + "Choose plan to fulfil dreams", + "Choose plan to fulfil goals", + "Choose plan to fulfil values", + ], +}; + +const normalise = (str: string) => + str + .toLowerCase() + .replace(/\s+/g, "") + .replace(/[^a-z0-9]/gi, ""); + +const capitaliseWords = (str: string) => + str.replace(/\b\w/g, (char) => char.toUpperCase()); + +const needsCategories = Object.keys(needsList).map((key) => ({ + id: uuidv4(), + name: capitaliseWords(key.replace(/([A-Z])/g, " $1").trim()), + timestamp: new Date().toISOString(), +})); + +const needs = Object.entries(needsList).flatMap(([categoryKey, items]) => { + const category = needsCategories.find( + (c) => normalise(c.name) === normalise(categoryKey) + ); + if (!category) throw new Error(`Category '${categoryKey}' not found`); + + return items.map((item) => ({ + id: uuidv4(), + name: item, + selectedTimestamps: [], + selectedExpiry: new Date().toISOString(), + category: category.id, + timestamp: new Date().toISOString(), + })); +}); + +const nextActions = needs.map((need) => { + const actionName = suggestAction(need.name); + return { + id: uuidv4(), + name: actionName, + need: need.id, + selectedTimestamps: [], + selectedExpiry: new Date().toISOString(), + timestamp: new Date().toISOString(), + }; +}); + +function suggestAction(needName: string): string { + const suggestions: Record = { + Air: "Find fresh air to breathe deeply.", + Food: "Prepare or purchase a nutritious meal.", + Water: "Drink a glass of water.", + Movement: "Stretch or take a short walk.", + Exercise: "Schedule time for a workout.", + "Protection (insects, bacteria, virus, predators)": + "Secure a safe environment.", + Rest: "Take a moment to relax and recharge.", + "Sexual expression": "Communicate openly about your needs.", + Shelter: "Ensure your space is clean and welcoming.", + Touch: "Reach out for a hug or comforting touch.", + Play: "Engage in a favorite game or activity.", + Laughter: "Watch something funny or joke with friends.", + Acceptance: "Practice self-acceptance through affirmations.", + Appreciation: "Express gratitude to someone you care about.", + Closeness: "Reach out to connect with a loved one.", + Community: "Participate in a group or social event.", + Consideration: "Take a moment to listen actively.", + "Contribution to the enrichment of life": + "Volunteer or offer help to someone in need.", + "Emotional safety": "Talk to a trusted friend about your feelings.", + Empathy: "Put yourself in someone else's shoes.", + Honesty: "Share your thoughts transparently.", + Love: "Spend quality time with someone close to you.", + Reassurance: "Ask for or provide encouragement.", + Respect: "Acknowledge someone's boundaries or contributions.", + Support: "Offer or ask for support from friends or family.", + Trust: "Engage in a trust-building activity.", + Understanding: "Seek to understand another's perspective.", + Warmth: "Create a cozy environment for yourself.", + "Celebration of life fulfilled": "Plan a small celebration.", + "Celebration of dreams fulfilled": + "Acknowledge and celebrate achievements.", + "Mourning of dreams unfulfilled": + "Reflect and journal about your emotions.", + "Mourning losses of loved ones": "Honor their memory in a meaningful way.", + Peace: "Meditate or spend time in a calm space.", + Order: "Organize your workspace or schedule.", + Harmony: "Seek resolution in lingering conflicts.", + Beauty: "Visit a place or object that inspires beauty.", + Inspiration: "Explore art or literature for new ideas.", + Authenticity: "Take a step that reflects your true self.", + Creativity: "Work on a creative project.", + Meaning: "Reflect on what gives you purpose.", + "Self worth": "Write a list of your positive attributes.", + "Choose dreams": "Visualize your ideal future.", + "Choose goals": "Set clear, actionable goals.", + "Choose values": "Identify your core values.", + "Choose plan to fulfil dreams": "Create a roadmap to achieve your dreams.", + "Choose plan to fulfil goals": "Break down your goals into steps.", + "Choose plan to fulfil values": "Act in alignment with your values.", + }; + + return suggestions[needName] || "Take a small step toward meeting this need."; +} + +export { needsCategories, needs, nextActions }; diff --git a/src/lib/db/seed/toolkit.ts b/src/lib/db/seed/toolkit.ts new file mode 100644 index 00000000..881f6ed5 --- /dev/null +++ b/src/lib/db/seed/toolkit.ts @@ -0,0 +1,55 @@ +import { v4 as uuidv4 } from "uuid"; + +export const categories = [ + { id: uuidv4(), name: "Replace", timestamp: new Date().toISOString() }, + { id: uuidv4(), name: "Barrier", timestamp: new Date().toISOString() }, + { id: uuidv4(), name: "Distract", timestamp: new Date().toISOString() }, + { + id: uuidv4(), + name: "Change Status", + timestamp: new Date().toISOString(), + }, +]; + +export const toolkit = [ + { + id: uuidv4(), + name: "Listen to my favourite music", + categories: ["Replace", "Barrier"], + checked: false, + infoUrl: "https://google.com/music", + imageUrl: + "https://daily.jstor.org/wp-content/uploads/2023/01/good_times_with_bad_music_1050x700.jpg", + timestamp: new Date().toISOString(), + }, + { + id: uuidv4(), + name: "Breathing exercises", + categories: ["Distract"], + checked: false, + infoUrl: "https://www.youtube.com/watch?v=DbDoBzGY3vo", + imageUrl: + "https://www.bhf.org.uk/-/media/images/information-support/heart-matters/2023/december/wellbeing/deep-breathing-620x400.png?h=400&w=620&rev=4506ebd34dab4476b56c225b6ff3ad60&hash=B3CFFEEE704E4432D101432CEE8B2766", + timestamp: new Date().toISOString(), + }, + { + id: uuidv4(), + name: "Call a friend", + categories: ["Distract", "Change status"], + checked: false, + infoUrl: "https://example.com/call", + imageUrl: + "https://t4.ftcdn.net/jpg/04/63/63/59/360_F_463635935_IweuYhCqZRtHp3SLguQL8svOVroVXvvZ.jpg", + timestamp: new Date().toISOString(), + }, + { + id: uuidv4(), + name: "Drink water", + categories: ["Distract", "Change status"], + checked: false, + infoUrl: "https://example.com/call", + imageUrl: + "https://content.health.harvard.edu/wp-content/uploads/2023/07/b8a1309a-ba53-48c7-bca3-9c36aab2338a.jpg", + timestamp: new Date().toISOString(), + }, +];