Skip to content

Commit

Permalink
Merge pull request #145 from MyLife-Services/132-mylife-alerts
Browse files Browse the repository at this point in the history
132-mylife-alerts
  • Loading branch information
Mookse authored Jan 15, 2024
2 parents 596533a + bb2485a commit 1e29f1e
Show file tree
Hide file tree
Showing 19 changed files with 408 additions and 49 deletions.
3 changes: 2 additions & 1 deletion .env-sample
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ MYLIFE_EMBEDDING_SERVER_BEARER_TOKEN={ chosen personally to match environment va
MYLIFE_EMBEDDING_SERVER_FILESIZE_LIMIT=1024 # 1MB = 1024 * 1024
MYLIFE_EMBEDDING_SERVER_FILESIZE_LIMIT_ADMIN=1024 # 10MB = 1024 * 1024 * 10
MYLIFE_CONTRIBUTIONS_DB_CONTAINER_NAME= # get from administrator
MYLIFE_REGISTRATION_DB_CONTAINER_NAME= # get from administrator
MYLIFE_REGISTRATION_DB_CONTAINER_NAME= # get from administrator
MYLIFE_SYSTEM_DB_CONTAINER_NAME= # get from administrator
16 changes: 12 additions & 4 deletions inc/js/core.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import EventEmitter from 'events'
import chalk from 'chalk'
// import { _ } from 'ajv'
// server-specific imports
import initRouter from './routes.js'
import initRouter from './routes.mjs'
import { _ } from 'ajv'
// define export Classes for Members and MyLife
class Member extends EventEmitter {
Expand Down Expand Up @@ -301,6 +301,17 @@ class MyLife extends Organization { // form=server
super(_Factory)
}
/* public functions */
/**
* Server MyLife _Maht instantiation uses this function to populate the most current alerts in the modular factory memoryspace. Currently only applicable to system types, but since this is implemented at the `core.mjs` scope, we can account
* @public
* @returns {void} returns nothing, performs operation
*/
getAlerts(){
this.factory.getAlerts()
}
async getMyLifeSession(){
return await this.factory.getMyLifeSession()
}
/**
* Registers a new candidate to MyLife membership
* @public
Expand All @@ -309,9 +320,6 @@ class MyLife extends Organization { // form=server
async registerCandidate(_candidate){
return await this.factory.registerCandidate(_candidate)
}
async getMyLifeSession(){
return await this.factory.getMyLifeSession()
}
/* getters/setters */
/**
* Gets MyLife agent role, refers to server entity Maht/MyLife
Expand Down
File renamed without changes.
7 changes: 6 additions & 1 deletion inc/js/factory-class-extenders/class-extenders.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,16 @@ function extendClass_avatar(_originClass,_references) {
if(!ctx?.state?.chatMessage)
throw new Error('No message provided in context')
this.setActiveCategory(ctx.state.chatMessage) // also sets evolver contribution
return mChat(
const _chat = await mChat(
this.#openai,
this,
ctx.state.chatMessage
)
const _activeAlerts = ctx.state.MemberSession.alerts()
if(_activeAlerts?.length){
_chat.alerts = ctx.state.MemberSession.alerts()
}
return _chat
}
/**
* Proxy for emitter.
Expand Down
12 changes: 10 additions & 2 deletions inc/js/functions.mjs
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
/* imports */
import fs from 'fs'
import oAIAssetAssistant from './agents/system/asset-assistant.mjs'
import { _ } from 'ajv'
/* module export functions */
async function about(ctx){
ctx.state.title = `About MyLife`
await ctx.render('about') // about
}
async function alerts(ctx){
// @todo: put into ctx the _type_ of alert to return, system use dataservices, member use personal
if(ctx.params?.aid){ // specific system alert
ctx.body = await ctx.state.MemberSession.alert(ctx.params.aid)
} else { // all system alerts
ctx.body = await ctx.state.MemberSession.alerts(ctx.request.body)
}
}
async function api_register(ctx){
const _registrationData = ctx.request.body
const {
const {
registrationInterests,
contact={}, // as to not elicit error destructuring
personalInterests,
Expand Down Expand Up @@ -239,6 +246,7 @@ function mSetContributions(ctx){
/* exports */
export {
about,
alerts,
api_register,
avatarListing,
category,
Expand Down
File renamed without changes.
24 changes: 22 additions & 2 deletions inc/js/mylife-agent-factory.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import {
extendClass_file,
extendClass_message,
} from './factory-class-extenders/class-extenders.mjs' // do not remove, although they are not directly referenced, they are called by eval in configureSchemaPrototypes()
import Menu from './menu.js'
import MylifeMemberSession from './session.js'
import Menu from './menu.mjs'
import MylifeMemberSession from './session.mjs'
import chalk from 'chalk'
import { _ } from 'ajv'
// modular constants
Expand All @@ -43,6 +43,9 @@ const vmClassGenerator = vm.createContext({
const dataservicesId = process.env.MYLIFE_SERVER_MBR_ID
const oDataservices = await new Dataservices(dataservicesId).init()
// Object of registered extension functions
const _alerts = {
system: await oDataservices.getAlerts(), // not sure if we need other types in global modular, but feasibly historical alerts could be stored here, etc.
}
const oExtensionFunctions = {
extendClass_avatar: extendClass_avatar,
extendClass_consent: extendClass_consent,
Expand Down Expand Up @@ -104,6 +107,20 @@ class AgentFactory extends EventEmitter{
// always look to server to challenge Access; this may remove need to bind
return await oDataservices.challengeAccess(this.mbr_id,_passphrase)
}
async getAlert(_alert_id){
const _alert = _alerts.system.find(alert => alert.id === _alert_id)
return _alert ? _alert : await oDataservices.getAlert(_alert_id)
}
/**
* Returns all alerts of a given type, currently only _system_ alerts are available. Refreshes by definition from the database.
* @param {string} _type
* @returns {array} array of current alerts
*/
async getAlerts(_type){
const _systemAlerts = await this.dataservices.getAlerts()
_alerts.system = _systemAlerts
return this.alerts
}
/**
* Gets factory to product appropriate avatar
* @param {Guid} _avatar_id
Expand Down Expand Up @@ -183,6 +200,9 @@ class AgentFactory extends EventEmitter{
return await this.dataservices.registerCandidate(_candidate)
}
// getters/setters
get alerts(){ // currently only returns system alerts
return _alerts.system
}
get contribution(){
return this.schemas.contribution
}
Expand Down
86 changes: 71 additions & 15 deletions inc/js/mylife-data-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
*/
// imports
import { _ } from "ajv"
import Datamanager from "./mylife-datamanager.js"
import PgvectorManager from "./mylife-pgvector-datamanager.js"
import Datamanager from "./mylife-datamanager.mjs"
import PgvectorManager from "./mylife-pgvector-datamanager.mjs"
import { Guid } from "js-guid"
/**
* The Dataservices class.
Expand Down Expand Up @@ -129,10 +129,40 @@ class Dataservices {
[{ name: '@email',
value: _email,
}],
'registration'
'registration',
)
return _?.[0]?.id??Guid.newGuid().toString() // needed to separate out, was failing
}
/**
* Retrieves a specific alert by its ID. _Currently placehoder_.
* @async
* @public
* @param {string} _alert_id - The unique identifier for the alert.
* @returns {Promise<Object>} The alert corresponding to the provided ID.
*/
async getAlert(_alert_id){
return await this.getItem(_alert_id, 'system')
}
/**
* Retrieves all system alerts. _Currently only works with system alerts, but intends to be expanded, refactor_.
* This method is typically used to get all alert entities under a specific object.
* @async
* @public
* @param {string} _object_id - The parent object ID to search for associated alerts.
* @returns {Promise<Array>} An array of alerts associated with the given parent ID.
*/
async getAlerts(){
const _paramsArray = [
{ name: '@being', value: 'alert' },
{ name: '@currentDate', value: new Date().toISOString() }
]
const _query = `SELECT * FROM c WHERE c.being = @being AND @currentDate >= c.timestampRange['start'] AND @currentDate <= c.timestampRange['end']`

return await this.datamanager.getItems(
{ query: _query, parameters: _paramsArray },
'system',
)
}
/**
* Retrieves a specific avatar by its ID.
* @async
Expand All @@ -141,7 +171,7 @@ class Dataservices {
* @returns {Promise<Object>} The avatar corresponding to the provided ID.
*/
async getAvatar(_avatar_id) {
return await this.getItems('avatar', undefined, [{ name: '@id', value: _avatar_id }])
return await this.getItem(_avatar_id)
}
/**
* Retrieves all avatars associated with a given parent ID.
Expand All @@ -152,7 +182,11 @@ class Dataservices {
* @returns {Promise<Array>} An array of avatars associated with the given parent ID.
*/
async getAvatars(_object_id) {
return await this.getItems('avatar', undefined, [{ name: '@object_id', value: _object_id }])
return await this.getItems(
'avatar',
undefined,
[{ name: '@object_id', value: _object_id }],
)
}
/**
* Retrieves the first chat associated with a given parent ID.
Expand All @@ -175,7 +209,11 @@ class Dataservices {
* @returns {Promise<Array>} An array of chat conversations associated with the given parent ID.
*/
async getChats(parent_id) {
let _chats = await this.getItems('conversation', ['exchanges'], [{ name: '@parent_id', value: parent_id }])
let _chats = await this.getItems(
'conversation',
undefined,
[{ name: '@parent_id', value: parent_id }],
)
if (!_chats.length) {
_chats = await this.pushItem({
mbr_id: this.mbr_id,
Expand All @@ -200,7 +238,7 @@ class Dataservices {
_being,
['questions'],
[{ name: '@category', value: _category }],
'contribution_responses'
'contribution_responses',
))
.map(_ => (_.questions))
.reduce((acc, val) => acc.concat(val), [])
Expand All @@ -212,10 +250,20 @@ class Dataservices {
* @async
* @public
* @param {string} _id - The unique identifier for the item.
* @param {string} _container_id - The container to use, overriding default: `Members`.
* @returns {Promise<Object>} The item corresponding to the provided ID.
*/
async getItem(_id) {
return await this.datamanager.getItem(_id)
async getItem(_id, _container_id) {
try{
return await this.datamanager.getItem(
_id,
_container_id,
)
}
catch(_error){
console.log('mylife-data-service::getItem() error')
console.log(_error, _id, _container_id,)
}
}
/**
* Retrieves items based on specified parameters.
Expand All @@ -224,25 +272,33 @@ class Dataservices {
* @param {string} _being - The type of items to retrieve.
* @param {array} [_selects=[]] - Fields to select; if empty, selects all fields.
* @param {Array<Object>} [_paramsArray=[]] - Additional query parameters.
* @param {string} _container_id - The container name to use, overriding default.
* @returns {Promise<Array>} An array of items matching the query parameters.
*/
async getItems(_being, _selects=[], _paramsArray=[], _container='members') { // _params is array of objects { name: '${varName}' }
async getItems(_being, _selects=[], _paramsArray=[], _container_id) { // _params is array of objects { name: '${varName}' }
// @todo: incorporate date range functionality into this.getItems()
const _prefix = 'u'
_paramsArray.unshift({ name: '@being', value: _being }) // add primary parameter to array at beginning
const _selectFields = (_selects.length)
? [...this.#rootSelect, ..._selects].map(_=>(`${_prefix}.`+_)).join(',')
: '*'
let _query = `select ${_selectFields} from ${_container} ${_prefix}` // @being is required
let _query = `select ${_selectFields} from ${_prefix}` // @being is required
_paramsArray // iterate through parameters
.forEach(_param=>{ // param is an object of name, value pairs
_query += (_param.name==='@being')
? ` where ${_prefix}.${_param.name.split('@')[1]}=${_param.name}` // only manages string so far
: ` and ${_prefix}.${_param.name.split('@')[1]}=${_param.name}` // only manages string so far
})
return await this.datamanager.getItems(
{ query: _query, parameters: _paramsArray },
_container,
)
try{
return await this.datamanager.getItems(
{ query: _query, parameters: _paramsArray },
_container_id,
)
}
catch(_error){
console.log('mylife-data-service::getItems() error')
console.log(_error, _being, _query, _paramsArray, _container_id,)
}
}
/**
* Retrieves local records based on a query.
Expand Down
29 changes: 15 additions & 14 deletions inc/js/mylife-datamanager.js → inc/js/mylife-datamanager.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* imports */
// import { DefaultAzureCredential } from "@azure/identity"
import { CosmosClient } from '@azure/cosmos'
import Config from './mylife-datasource-config.js'
import Config from './mylife-datasource-config.mjs'
import chalk from 'chalk'
// define class
class Datamanager {
Expand All @@ -19,13 +19,14 @@ class Datamanager {
// aadCredentials: new DefaultAzureCredential()
}
const _client = new CosmosClient(_options)
this.database = _client.database(_config.db.id)
this.#partitionId = _config.db.container.partitionId
this.#coreId = _config.db.container?.coreId??this.#partitionId.split('|')[1]
this.database = _client.database(_config.members.id)
this.#partitionId = _config.members.container.partitionId
this.#coreId = _config.members.container?.coreId??this.#partitionId.split('|')[1]
this.#containers = {
contribution_responses: this.database.container(_config.contributions.container.id),
members: this.database.container(_config.db.container.id),
members: this.database.container(_config.members.container.id),
registration: this.database.container(_config.registration.container.id),
system: this.database.container(_config.system.container.id),
}
this.requestOptions = {
partitionKey: this.#partitionId,
Expand Down Expand Up @@ -74,32 +75,32 @@ class Datamanager {
* Retreives specific item from container.
* @param {guid} _id
* @param {object} _options
* @param {string} _container Container to use.
* @param {string} _container_id Container to use.
* @returns
*/
async getItem(_id, _options=this.requestOptions, _container=this.containerDefault){ // quick, inexpensive read; otherwise use getItems
const { resource: _item } = await this.#containers[_container]
async getItem(_id, _container_id=this.containerDefault, _options=this.requestOptions){ // quick, inexpensive read; otherwise use getItems
const { resource: _item } = await this.#containers[_container_id]
.item(_id,this.#partitionId)
.read(_options)
return _item
}
async getItems(_querySpec, _container=this.containerDefault, _options=this.requestOptions ){
const { resources } = await this.#containers[_container]
async getItems(_querySpec, _container_id=this.containerDefault, _options=this.requestOptions ){
const { resources } = await this.#containers[_container_id]
.items
.query(_querySpec,_options)
.fetchAll()
return resources
}
async patchItem(_id, _item, _container=this.containerDefault) { // patch or update, depends on whether it finds id or not, will only overwrite fields that are in _item
async patchItem(_id, _item, _container_id=this.containerDefault) { // patch or update, depends on whether it finds id or not, will only overwrite fields that are in _item
// [Partial Document Update, includes node.js examples](https://learn.microsoft.com/en-us/azure/cosmos-db/partial-document-update)
if(!Array.isArray(_item)) _item = [_item]
const { resource: _update } = await this.#containers[_container]
const { resource: _update } = await this.#containers[_container_id]
.item(_id,this.#partitionId)
.patch(_item) // see below for filter-patch example
return _update
}
async pushItem(_item, _container=this.containerDefault) { // post or insert, depends on whether it finds id or not, will overwrite all existing fields
const { resource: doc } = await this.#containers[_container]
async pushItem(_item, _container_id=this.containerDefault) { // post or insert, depends on whether it finds id or not, will overwrite all existing fields
const { resource: doc } = await this.#containers[_container_id]
.items
.upsert(_item)
return doc
Expand Down
Loading

0 comments on commit 1e29f1e

Please sign in to comment.