Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Azure deploy prod -> base #162

Merged
merged 7 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 0 additions & 31 deletions .env-sample

This file was deleted.

188 changes: 188 additions & 0 deletions inc/js/api-functions.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import chalk from "chalk"
/* variables */
const mBotSecrets = JSON.parse(process.env.OPENAI_JWT_SECRETS)
/* private modular functions */
async function _keyValidation(ctx){ // transforms ctx.state
if(ctx.params.mid === ':mid') ctx.params.mid = undefined
// ctx session alternatives to hitting DB every time? can try...
const _mbr_id = ctx.params.mid??ctx.request.body.memberKey
const _validated =
( // session validation shorthand
( ctx.session?.isAPIValidated??false )
&& _mbr_id?.length
&& ( _mbr_id===ctx.session?.APIMemberKey??false )
) || ( // initial full validation
(_mbr_id?.length)
&& process.env.MYLIFE_HOSTED_MBR_ID.includes(_mbr_id)
&& await ctx.MyLife.testPartitionKey(_mbr_id)
)
ctx.state.mbr_id = _mbr_id
ctx.state.assistantType = _tokenType(ctx)
ctx.state.isValidated = _validated
ctx.session.isAPIValidated = ctx.state.isValidated
ctx.session.APIMemberKey = ctx.state.mbr_id
}
function _tokenType(ctx){
const _token = ctx.state.token
const _assistantType = mBotSecrets?.[_token]??'personal-avatar'
return _assistantType
}
function _tokenValidation(_token){
return mBotSecrets?.[_token]?.length??false
}
/* public modular functions */
async function keyValidation(ctx){
await _keyValidation(ctx)
if(!ctx.state.isValidated){
ctx.status = 400 // Bad Request
ctx.body = {
success: false,
message: 'Invalid member.',
}
return
}
ctx.status = 200 // OK
if(ctx.method === 'HEAD') return
// @todo: determine how to instantiate avatar via Maht Factory--session? In any case, perhaps relegate to session
const _memberCore = await ctx.MyLife.datacore(ctx.state.mbr_id)
const { updates, interests, birth, birthDate, fullName, names, nickname } = _memberCore
const _birth = (Array.isArray(birth) && birth.length)
? birth[0]
: birth??{}
_birth.date = birthDate??_birth.date??''
_birth.place = _birth.place??''
const _memberCoreData = {
mbr_id: ctx.state.mbr_id,
updates: updates??'',
interests: interests??'',
birthDate: _birth.date,
birthPlace: _birth.place,
fullName: fullName??names?.[0]??'',
preferredName: nickname??names?.[0].split(' ')[0]??'',
}
ctx.body = {
success: true,
message: 'Valid member.',
data: _memberCoreData,
}
console.log(chalk.yellowBright(`keyValidation():${_memberCoreData.mbr_id}`), _memberCoreData.fullName)
return
}
async function register(ctx){
const _registrationData = ctx.request.body
const {
registrationInterests,
contact={}, // as to not elicit error destructuring
personalInterests,
additionalInfo
} = _registrationData
const {
avatarName,
humanName,
humanDateOfBirth,
email,
city,
state,
country,
} = contact
if (!humanName?.length || !email?.length){
ctx.status = 400 // Bad Request
ctx.body = {
success: false,
message: 'Missing required contact information: humanName and/or email are required.',
}
return
}
// Email validation
if (!ctx.Globals.isValidEmail(contact.email)) {
ctx.status = 400 // Bad Request
ctx.body = {
success: false,
message: 'Invalid email format.',
}
return
}
// throttle requests?
// write to cosmos db
_registrationData.email = email // required at root for select
const _ = ctx.MyLife.registerCandidate(_registrationData)
const { mbr_id, ..._return } = _registrationData // abstract out the mbr_id
ctx.status = 200
ctx.body = {
success: true,
message: 'Registration completed successfully.',
data: _return,
}
return
}
/**
* Functionality around story contributions and portrayals
* @param {Koa} ctx - Koa Context object
* @returns {Koa} Koa Context object
*/
async function story(ctx){
await _keyValidation(ctx) // sets ctx.state.mbr_id and more
const { assistantType, mbr_id } = ctx.state
const { storySummary } = ctx.request?.body??{}
if(!storySummary?.length){
ctx.status = 400 // Bad Request
ctx.body = {
success: false,
message: 'No story summary provided. Use `storySummary` field.',
}
return
}
// write to cosmos db
const _story = await ctx.MyLife.story(mbr_id, assistantType, storySummary) // @todo: remove await
console.log(chalk.yellowBright('story submitted:'), _story)
ctx.status = 200 // OK
ctx.body = {
success: true,
message: 'Story submitted successfully.',
story: _story,
}
return
}
/**
* Validates api token
* @modular
* @public
* @param {object} ctx Koa context object
* @param {function} next Koa next function
* @returns {function} Koa next function
*/
async function tokenValidation(ctx, next) {
try {
const authHeader = ctx.request.headers['authorization']
if(!authHeader){
ctx.status = 401
ctx.body = { error: 'Authorization header is missing' }
return
}
const _token = authHeader.split(' ')[1] // Bearer TOKEN_VALUE
if(!_tokenValidation(_token)){
ctx.status = 401
ctx.body = { error: 'Authorization token failure' }
return
}
ctx.state.token = _token // **note:** keep first, as it is used in _tokenType()
ctx.state.assistantType = _tokenType(ctx)
await next()
} catch (error) {
ctx.status = 401
const _error = {
name: error.name,
message: error.message,
stack: error.stack
}
ctx.body = { message: 'Unauthorized Access', error: _error }
return
}
}
/* exports */
export {
keyValidation,
register,
story,
tokenValidation,
}
35 changes: 35 additions & 0 deletions inc/js/core.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,10 @@ class MyLife extends Organization { // form=server
constructor(_Factory){ // no session presumed to exist
super(_Factory)
}
async datacore(_mbr_id){
if(!_mbr_id || _mbr_id===this.mbr_id) throw new Error('datacore cannot be accessed')
return await this.factory.datacore(_mbr_id)
}
/* 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
Expand All @@ -299,6 +303,37 @@ class MyLife extends Organization { // form=server
async registerCandidate(_candidate){
return await this.factory.registerCandidate(_candidate)
}
/**
* Submits a story to MyLife via API. Unclear if can be dual-purposed for internal, or if internal still instantiates API context.
* @public
* @param {string} _mbr_id - Member id
* @param {string} _assistantType - String name of assistant type
* @param {string} _summary - String summary of story
* @returns {object} - The story document from Cosmos.
*/
async story(_mbr_id, _assistantType, storySummary){
const id = this.globals.newGuid
const _story = {
assistantType: _assistantType,
being: 'story',
form: _assistantType,
id,
mbr_id: _mbr_id,
name: `${_assistantType}-story_${_mbr_id}_${id}`,
summary: storySummary,
}
const _storyCosmos = await this.factory.story(_story)
return this.globals.stripCosmosFields(_storyCosmos)
}
/**
* Tests partition key for member
* @public
* @param {string} _mbr_id member id
* @returns {boolean} returns true if partition key is valid
*/
async testPartitionKey(_mbr_id){
return await this.factory.testPartitionKey(_mbr_id)
}
/* getters/setters */
/**
* Gets MyLife agent role, refers to server entity Maht/MyLife
Expand Down
48 changes: 0 additions & 48 deletions inc/js/functions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,53 +27,6 @@ async function alerts(ctx){
ctx.body = await ctx.state.MemberSession.alerts(ctx.request.body)
}
}
async function api_register(ctx){
const _registrationData = ctx.request.body
const {
registrationInterests,
contact={}, // as to not elicit error destructuring
personalInterests,
additionalInfo
} = _registrationData
const {
avatarName,
humanName,
humanDateOfBirth,
email,
city,
state,
country,
} = contact
if (!humanName?.length || !email?.length){
ctx.status = 400 // Bad Request
ctx.body = {
success: false,
message: 'Missing required contact information: humanName and/or email are required.',
}
return
}
// Email validation
if (!ctx.Globals.isValidEmail(contact.email)) {
ctx.status = 400 // Bad Request
ctx.body = {
success: false,
message: 'Invalid email format.',
}
return
}
// throttle requests?
// write to cosmos db
_registrationData.email = email // required at root for select
const _ = ctx.MyLife.registerCandidate(_registrationData)
const { mbr_id, ..._return } = _registrationData // abstract out the mbr_id
ctx.status = 200
ctx.body = {
success: true,
message: 'Registration completed successfully.',
data: _return,
}
return
}
async function avatarListing(ctx){
ctx.state.title = `Avatars for ${ ctx.state.member.memberName }`
ctx.state.avatars = []
Expand Down Expand Up @@ -278,7 +231,6 @@ export {
about,
activateBot,
alerts,
api_register,
avatarListing,
bots,
category,
Expand Down
3 changes: 3 additions & 0 deletions inc/js/globals.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class Globals extends EventEmitter {
isValidGuid(_str='') {
return (typeof _str === 'string' && guid_regex.test(_str))
}
stripCosmosFields(_obj){
return Object.fromEntries(Object.entries(_obj).filter(([k, v]) => !k.startsWith('_')))
}
sysId(_mbr_id){
if(!typeof _mbr_id==='string' || !_mbr_id.length || !_mbr_id.includes('|'))
throw new Error('expected MyLife member id string')
Expand Down
Loading
Loading