5FJK2FV6GEVYXYIA757UE2SKX2XPLZKYVBHN47EMK6UC7FO3L23QC import http from 'http'class Server {constructor(client) {this.client = clientthis.server = http.createServer(async function(request, response) {switch (request.method) {case "GET": {const user_path = request.urlconst found = !!(await client.read(`state${user_path}/email`))if (found) {response.writeHead(204, "User found")response.end()} else {response.writeHead(404, "User not found")response.end()}} break}})}async start() {this.server.listen(25608)}close() {this.server.close()}}export { Server }
import { Server } from './server.js'import { Client } from '@djinlist/ipc/client'import { DATASTORE_PIPENAME } from '@djinlist/env'async function main() {const client = new Client('User Portal', DATASTORE_PIPENAME)await client.connect()const server = new Server(client)await server.start()}main()
{"name": "@djinlist/services_user_portal","version": "1.0.0","type": "module","license": "UNLICENSED","private": true,"main": "src/index.js","dependencies": {"ws": "^6.0.0","uuid": "^8.0.0","@djinlist/ipc": "1.0.0","@djinlist/env": "1.0.0"},"scripts": {"start": "node src/index.js"},"exports": {".": "./src/index.js"}}
import fs from 'fs'class Stash {constructor() {console.log(`${this.toString()} #constructor`)setInterval(this.stash.bind(this), 5000)}toString() {return 'Model-Stash'}stash() {console.log(`${this.toString()} #stash`)const tree = {}const state = datastore.read('/state')const setup = datastore.read('/setup')if (state) Object.assign(tree, { state })if (setup) Object.assign(tree, { setup })fs.writeFile('./.stash.json',JSON.stringify(tree, null, 2),(error) => {if (!error) returnconsole.log("Error Writing to Stash")console.log(error)})}destroy() {clearInterval(this.interval)}}const stash = new Stash()const cleanup = () => { stash.destroy() }process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)export { stash }
import fs from 'fs'import { Datastore } from '@controlenvy/datastore'import { Server } from '@djinlist/ipc/server'import { DATASTORE_PIPENAME } from '@djinlist/env'if (!global.datastore) global.datastore = new Datastore()async function loadConfig() {// configuration information goes heredatastore.set('/session/node_id', '952ede89-4c91-4df7-bdab-c6dda4257abb')function onFileFound(file, data) {if (!data) { return false }const _root = JSON.parse(data)if (Object.keys(_root).length === 0) { return false }console.log(`index.js Parsing root found at ${file}`)datastore.merge('/state', _root.state)datastore.merge('/setup', _root.setup)return true}['.stash.json', '.preconfig.json'].find(filePath => {try {return onFileFound(filePath, fs.readFileSync(filePath))} catch (e) {console.log(`Error reading config at ${filePath}`)console.log(e)return false}})}function main() {const server = new Server(DATASTORE_PIPENAME, datastore)server.start()function cleanup() {server.cleanup()process.removeListener('SIGINT', cleanup)process.removeListener('SIGTERM', cleanup)process.exit()}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)}await loadConfig()import './stash.js'main()
{"name": "@djinlist/services_datastore","version": "1.0.0","type": "module","license": "UNLICENSED","private": true,"main": "src/index.js","scripts": {"start": "node src/index.js"},"dependencies": {"@djinlist/ipc": "^1.0.0","@djinlist/env": "^1.0.0","secure-remote-password": "^0.3.1"},"exports": {".": "./src/index.js"}}
{"state": {"users": {"8e97bb55-8b08-5eea-b6d8-3456ec25d301": {"auth": {"salt": "0b7a988a178f368841cd2f3c05d407d18de26dba6b78e3afb5967c71d9b146d2","verifier": "8f935c53fa9b18082f8a06060fa46064be5978adde30aafa89083ac5b25552685f58a33be35cc3f697dab8ceccba18320678ace3970e1a082c7b61117a6af4301a93f40e4c73f26302b10cfbb0fa7f2acb99df708c3eae77ad4f6ae62753692f91e2e76e48cae12a1206eced2feefea1acd0697697c3853248798f791ee30a35e842bea12bcca853ebee71a31a660a8eba5fbdc4b8a2859fabfb8adf8f97717371e8b415057726f78599ceda50413683cfcf3bf5885d8e70831d90c8afabb2d935b62970b1e4f77227a55d0916c65a6b91db329f1903fbbaf5d7fdd6d5ffa3332b50b36db5fc00fb186696adf16f23f0a6fcbcbdb73ce947326b660e0663078a","client_public_key": "2ac46f9b921e0d6bceeb021824784acee3ef3f493fde38bdd837cfa844acb21fab549729b4d75891cf05fed07b46bd0464be37f95c2a9f700dff6abc8559187bf9c12d46c4e19fc71d1c6c852d37fe13869d2e6ec6c1729a7c7ab4d8e5aef7d3088fe65e1cd07d43c6b8d08a7b2dcb624b397bdf9c1480f7ace54d7f37e4d5f7b59863ecbb6dbc97c3b28133ecabe5c45f44cd64e27e613635d193fc23033b936318157101fa72fc8c463e0a3e19353ddac1ffafaf2fef1e3a53903434e02dcbf4a17f482e1288ec29a0712a8660d772d9adbf51794e6781888d08df819cac114645f30586f84fe192dee0b974a16af1f4c26d498cf50731854b29fa737b3723","server_secret_key": "a9d7ffbcee7ea9394b00f2d050ce484c27049b69e9eb31f445a10aff605e1a41","session_key": "57ff063d88ffe91e306dec874e5ff9471dad9e03d9ba249fc9cf085e7481153b"},"email": "thomas.p.cowan@gmail.com","name": "tpcowan","created_at": 1639825217687,"services": {"spotify": {"client": {"expiry": 1644813102,"token": "BQAXF6uKCYJnzkJ-zI3wqCAKgKidMWj0jl7swTJVBkX0EC6t6nXyX8CMdLCfk3Jr29CjqBO6W_azFdl_NHNVXHcVqRQh-25LtqLZWjTbIxnur9NOEqUrn0h43Zc04-ZeL77GuByGUn9sG0yPzdm8IuN81N3DO8OAi--LkPzhIxlRydpYQ_ppxiyF5TvzDw"}}},"followed": ["/events/62854dc2-7d97-45d3-be03-f0bac69119f8"],"topics": ["+/events/56bf15ad-0b26-4fd9-b838-aebe8d73b244/#","+/events/a7313474-90e4-4938-b59d-6eb7a59a0f68/#"],"owned": ["/events/56bf15ad-0b26-4fd9-b838-aebe8d73b244","/events/a7313474-90e4-4938-b59d-6eb7a59a0f68"],"event_id": "/events/62854dc2-7d97-45d3-be03-f0bac69119f8"},"919340b1-1217-548e-914b-a876f726b62a": {"auth": {"client_public_key": "a9af1bcbea8f295550dda1a5bf491e3e6e586cc6b9297ec629911fef567f19c2e82dbbab07e9cf5e0de0106a29fe2a3e05aecde84e0d326894e018096f99a8a0869a51146fa253f79e4859b7e93ea37fd3cc589a7ef0f3129e087d190627a7b9e63fbea7a233232651195943f02fa2d86244c220ce457d65ee9985edacb9126c52c92e16dea1064d0f1a2b6b7d0c5b1f49ea4872cc0d0def94198b11731d4e2296368a5ab35b587ca33bd5a19de124c6f2f6a3511589f39e98ecd1e8b3b2f283496aae91ce6fc440117e8cdd492d09796c951c2819faa3934ea6e4cf1585ccf5b54947a62adf7ecbede8d77d5bfbaeec77618fbeab673fafa72933158480efe6"}}}},"setup": {"events": {"75c6cfd0-139a-4a33-8826-9c284645f1ae": {"name": "Calgary Weekly List"},"62854dc2-7d97-45d3-be03-f0bac69119f8": {"name": "Toronto Weekly List"},"fe71a1ee-6e64-4d4f-8a03-7b091d93c823": {"name": "Belgium Weekly List"},"56bf15ad-0b26-4fd9-b838-aebe8d73b244": {"private": true,"owner": "8e97bb55-8b08-5eea-b6d8-3456ec25d301","name": "My Birthday","admin": ["8e97bb55-8b08-5eea-b6d8-3456ec25d301"],"users": ["8e97bb55-8b08-5eea-b6d8-3456ec25d301"]},"a7313474-90e4-4938-b59d-6eb7a59a0f68": {"private": true,"owner": "8e97bb55-8b08-5eea-b6d8-3456ec25d301","name": "Event two","time": 1643082402000,"description": "What's up","admin": ["8e97bb55-8b08-5eea-b6d8-3456ec25d301"],"users": ["8e97bb55-8b08-5eea-b6d8-3456ec25d301"]}}}}
{"setup": {"events": {"75c6cfd0-139a-4a33-8826-9c284645f1ae": {"name": "Calgary Weekly List"},"62854dc2-7d97-45d3-be03-f0bac69119f8": {"name": "Toronto Weekly List"},"fe71a1ee-6e64-4d4f-8a03-7b091d93c823": {"name": "Belgium Weekly List"}}}}
import crypto from "crypto"import { forEach, find } from 'lodash-es'class Session {constructor() {this.cleaner = setInterval(this.clean.bind(this), 1000 * 60 * 60)this.key = '2390fbad9157e1f8de9ecb5a494feeb988dfe2ba31c39ca1ba91ba2fa9d30d20'}clean() {const timeouts = datastore.read('/session/connections/+/tokens/+')forEach(timeouts, (timeout, path) => {if (+ new Date < timeout) { return }const pointer = new Pointer(path.replace('/timeout', ''))datastore.destroy(pointer.path)})}fetchSession(cookie, ws, channel) {console.log(`fetchSession\n\ntoken:\n${cookie}`)const response = {o: 'a',c: 5,v: false}// {// tokens: ,// topics: []// }const [user, token, mac] = cookie.split(':')const verify = crypto.createHmac('sha256',user + ':' + token,this.key).digest('hex')const session = datastore.read(`/session/connections/${user}`)const topics = datastore.read(`/state/users/${user}/topics`) || []console.log({session, topics})if (!session) returnif (crypto.timingSafeEqual(Buffer.from(verify, 'utf8'), Buffer.from(mac, 'utf8')) &&session && session.tokens) {find(session.tokens, (timestamp, stored_token) => {if (crypto.timingSafeEqual(Buffer.from(stored_token, 'utf8'), Buffer.from(token, 'utf8')) &×tamp > + new Date) {response.v = { u: user, s: topics }return true}})}if (response.v) {forEach(topics, pattern => {const topic = new Topic(pattern)channel.resume(topic, ws)})channel.resume(new Topic(`+/users/${user}/#`), ws)datastore.write(`/session/connections/${user}/ws`, ws.toString())}ws.send(JSON.stringify(response))}requestSession(user, ws, client) {if (!client.isAuthorized(new Topic(`state/users/${user}/#`), ws)) {return}const token = crypto.randomBytes(256).toString('hex')const cookie = user + ':' + tokenconsole.log(`postSession for ${user} \n\ntoken:\n${token}`)const mac = crypto.createHmac('sha256',user + ':' + token,this.key).digest('hex')datastore.write(`/session/connections/${user}/tokens/${token}`, + new Date + 60 * 60 * 24 * 7 * 1000)datastore.write(`/session/connections/${user}/ws`, ws.toString())const response = {o: 'a',c: 6,v: cookie + ':' + mac}ws.send(JSON.stringify(response))}addSubscription(user, topic) {console.log(`#addSubscription ${user}, ${topic.pattern}`)const topics = new Set(datastore.read(`/state/users/${user}/topics`) || [])topics.add(topic.pattern)datastore.write(`/state/users/${user}/topics`, Array.from(topics))}toString() {return 'Channel-Connections'}}const session = new Session()export { session }
import WebSocket from 'ws'import { v4 as uuid } from 'uuid'import { forEach } from 'lodash-es'import './models/item.js'import './models/event.js'import './models/user.js'import './models/admin.js'import './models/spotify.js'import { session } from './session.js'import { client } from './channels/client.js'let main = () => {/* Models *//* currently empty *//* IPC Sockets */const port = 25706const wss = new WebSocket.Server({ port })console.log(`Listenning on ${port}`)wss.on('connection', function connection(ws, request) {const ip = request.socket.remoteAddressws.uuid = uuid()ws.toString = () => ws.uuidconsole.log(`Connection from ${ip}`)datastore.push('/session/connections', ip)const node_id = datastore.read('/session/node_id')const hello = {}hello.o = 'w'hello.v = node_idhello.p = '/session/node_id'ws.send(JSON.stringify(hello))const publish = (topic, pointer) => {const message = {}const { tree, path } = pointerconst { value } = treemessage.o = 'p'message.t = topicmessage.p = pathmessage.v = valueconsole.log(`sending: ${JSON.stringify(message)}`)ws.send(JSON.stringify(message))}ws.on('message', (message) => {console.log(message)message = JSON.parse(message)if (!message.o) {return}let pointer, topic, channelswitch (message.o) {case 'a': {switch (message.c) {case 0: {// createAccounttopic = new Topic(message.t)client.createAuthorization(topic, message.v, ws)} breakcase 1: {// startSessiontopic = new Topic(message.t)client.authorize(topic, message.v, ws)} breakcase 4: {// verifySessiontopic = new Topic(message.t)if (client.prove(topic, message.v, ws)) {session.addSubscription(message.v.u, topic)}} breakcase 5: {// post sessionsession.fetchSession(message.v, ws, client)} breakcase 6: {// fetch sessionsession.requestSession(message.v, ws, client)} breakdefault:break}} breakcase 'm': {pointer = new Pointer(message.p)client.merge(pointer, message.v, ws)} breakcase 'w': {pointer = new Pointer(message.p)client.write(pointer, message.v, ws)} breakcase 'd': {pointer = new Pointer(message.p)client.delete(pointer, ws)} breakcase 's': {topic = new Topic(message.t)console.log({topic})client.subscribe(topic, ws, publish)if (!message.i) { break }console.log('IMMEDIATE')message.p = `/${message.t}`}case 'r': {pointer = new Pointer(message.p)forEach(client.read(pointer, ws), (value, path) => {const response = { o: 'r' }response.p = pathresponse.v = valuews.send(JSON.stringify(response))})} breakcase 'u': {topic = new Topic(message.t)client.unsubscribe(topic, ws, publish)if (!message.i) break}}})ws.on('close', () => {datastore.pull('/session/connections', ip)client.unsubscribe(ws)console.log('disconnected')})})const cleanup = () => {console.log('\rShutting down ws server') // eslint-disable-line no-consoleprocess.removeListener('SIGINT', cleanup)process.removeListener('SIGTERM', cleanup)wss.close(() => {process.exit()})}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)}main()
import { v4 as uuid } from 'uuid'class User {constructor() {console.log(`${this.toString()} #constructor`)datastore.subscribe('q/state/users/+/#', this.onStateQueued.bind(this))datastore.subscribe('q/action/users/+/events/new', this.onEventCreate.bind(this))datastore.subscribe('q/setup/users/+/events/+/+', this.onEventSetup.bind(this))}toString() {return 'Model-Users'}onStateQueued(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valueconsole.log(`${this.toString()} #onStateQueued: ${pointer.path}, ${JSON.stringify(value)}`)if (!value || event.type !== '=') { return }if (pointer.leaf == 'pin') {// FIXME: @thomascowan what purpose does this serve? 09/20/2021this.accept(pointer, value)pointer.leaf = 'srp'datastore.set(pointer.path, 'authenticated')} else if (pointer.steps[4] === 'events') {switch (pointer.leaf) {case 'vote': {// NOTE: if the leaf is 'vote', then transfer the vote to the eventconst event_id = datastore.read(`/state${pointer.trunk_path}/event_id`)console.log('event_id', pointer.branch_path, `/state${pointer.trunk_path}/event_id`, event_id)if (!event_id) { return }datastore.set(pointer, null, { silent: true })console.log(`Transfer vote to /q/state/${pointer.branch.slice(2,4).join('/')}${pointer.trunk_path}/${pointer.branch.slice(-2).join('/')}`)datastore.set(`/q/state/${pointer.branch.slice(2,4).join('/')}${pointer.trunk_path}/${pointer.branch.slice(-2).join('/')}`, value)datastore.set(pointer, null, { silent: true })} breakcase 'pin': {datastore.read(`/setup/${pointer.branch.slice(2,4).join('/')}/owner`)} break}} else {this.accept(pointer, value)}}// What are the basic actions that can be taken on an event? Assuming there are three levels// of permissions that are consistent across all event types.// (These event types can be elaborated and parse out by reading a 'type' leaf later)// 1. Owner// 2. Admin// - add admin (action) v// - change name (setup) v// - splice// - insert// - blacklist (setup) v// - whitelist (setup) v// - ban user (setup) v// - unban user (setup) v// - change date (setup)// - description (setup) v// 3. Participant// - vote//// 'q/setup/users/+/events/+/+'onEventSetup(topic, event) {console.log('onEventSetup', event.pointer.path)if (event.type !== '=') returnconst { pointer } = eventswitch(pointer.leaf) {// leafs that don't require validationcase 'name':case 'description':case 'private': {if (this.permissionFor('admin', pointer.steps[3], pointer.steps[5])) {console.log(`/setup${event.pointer.sliceBranch(2)}/${pointer.leaf}`, pointer.tree.value)datastore.write(`/setup${event.pointer.sliceBranch(2)}/${pointer.leaf}`, pointer.tree.value)}} break// leafs that require some validationcase 'pin': {if (this.permissionFor('admin', pointer.steps[3], pointer.steps[5])) {datastore.push(`/setup/events/${pointer.setps[5]}/blacklist`, pointer.tree.value)}} breakcase 'time': {if (this.permissionFor('admin', pointer.steps[3], pointer.steps[5])) {datastore.push(`/setup/events/${pointer.setps[5]}/blacklist`, pointer.tree.value)}} breakcase 'type': {if (!['recurring', 'one_time'].includes(pointer.value)) returnthis.accept(pointer, pointer.tree.value)} break// special casescase 'blacklist': {if (this.permissionFor('admin', pointer.steps[3], pointer.steps[5])) {datastore.push(`/setup/events/${pointer.setps[5]}/blacklist`, pointer.tree.value)}} breakcase 'whitelist': {if (this.permissionFor('admin', pointer.steps[3], pointer.steps[5])) {datastore.pull(`/setup/events/${pointer.setps[5]}/blacklist`, pointer.tree.value)}} breakcase 'ban': {if (this.permissionFor('admin', pointer.steps[3], pointer.steps[5])) {const admins = datastore.read(`/setup/events/${pointer.steps[5]}/admin`)if (admins.includes(event.pointer.value)) {if (this.permissionFor('user', pointer.steps[3], pointer.steps[5])) {datastore.pull(`/setup/events/${pointer.setps[5]}/admin`, pointer.tree.value)datastore.push(`/setup/events/${pointer.setps[5]}/banned`, pointer.tree.value)}} else {datastore.pull(`/setup/events/${pointer.setps[5]}/banned`, pointer.tree.value)}}} breakcase 'unban': {if (this.permissionFor('admin', pointer.steps[3], pointer.steps[5])) {datastore.pull(`/setup/events/${pointer.setps[5]}/banned`, pointer.tree.value)}} break}}permissionFor(level, user, event) {if (level === 'owner') {return datastore.read(`/setup/events/${event}/owner`) === user} else if (level === 'admin') {return (datastore.read(`/setup/events/${event}/admin`) || []).includes(user)} else {return (datastore.read(`/setup/events/${event}/users`) || []).includes(user)}}onEventCreate(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valueif (!value) return// NOTE: check the account type and the number of owned events// TODO: @thomascowan add support for account typesconst account_type = datastore.read(`/setup${pointer.trunk_path}/type`) || 'free'const owned = datastore.read(`/setup${pointer.trunk_path}/events/owned`) || []// NOTE: check that the user meets the criteria required to create a new eventswitch (account_type) {case 'business': {// NOTE: Placeholder do nothing for now} breakcase 'basic': {if (owned.length >= 5) return} breakcase 'free': {if (owned.length >= 3) return}}// NOTE: create an event with the user_idconst user_id = pointer.steps[3]const new_event_id = uuid()datastore.push(`/state/users/${user_id}/topics`, `+/events/${new_event_id}/#`, { queue: false })datastore.push(`/state/users/${user_id}/owned`, `/events/${new_event_id}`, { queue: false })datastore.write(`/setup/events/${new_event_id}/private`, true)datastore.write(`/setup/events/${new_event_id}/owner`, user_id)datastore.write(`/setup/events/${new_event_id}/admin`, [user_id])datastore.write(`/setup/events/${new_event_id}/users`, [user_id])datastore.write(`/setup/events/${new_event_id}/users`, [user_id])}onEventPin(topic, event) {if (event.type !== '=') {return}const { pointer } = eventconst value = pointer?.tree?.valueif (!this.validatePin(value)) {return}const owners = datastore.read(`/state/${pointer.branch.slice(2,4).join('/')}/owners`)const user_id = pointer.steps[3]if (!owners.includes(user_id)) {return}datastore.write(`/state${pointer.branch.slice(2,4).join('/')}/pin`, value)}validatePin(pin) {/\d{4}/.test(pin) // FIXME: @thomascowan Improve available logic for pins}accept(pointer, value, { force = false } = {}) {datastore.set(pointer.path, null, { silent: true })const dequeued_pointer = pointer.dequeue()datastore.set(dequeued_pointer.path, value, { force })}destroy() {}}const users = new User()const cleanup = () => {users.destroy()}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)export { users }
import https from 'https'import { debounce, reduce, concat } from 'lodash-es'class Driver {constructor() {datastore.subscribe('action/events/+/start', this.initializePlaylist.bind(this))this.debouncePushPlaylistDetails = debounce(this.pushPlaylistDetails, 1000)datastore.subscribe('action/events/+/name', this.debouncePushPlaylistDetails.bind(this))datastore.subscribe('action/events/+/description', this.debouncePushPlaylistDetails.bind(this))datastore.subscribe('q/state/events/+/playlist', this.onPlaylistItemsQueued.bind(this))}toString() {return 'Driver-Spotify-Database'}token() {return datastore.read('/setup/admin/token')}initializePlaylist(topic, event) {const { pointer } = eventconsole.log(`#initialize_playlist ${pointer.path}`)const eventId = pointer.branch.slice(-1)[0]const name = eventIdconst admin_id = datastore.read("/setup/admin/id")const body = {name,public: true,collaborative: false}const request = https.request({method: 'POST',hostname: 'api.spotify.com',path: `/v1/users/${admin_id}/playlists`,headers: {'Authorization': `Bearer ${this.token()}`,'Content-Type': 'application/json'}},response => {console.log(response.statusCode)if (![200, 201].includes(response.statusCode)) { return }let body = ''response.on('data', chunk => {console.log(body)body += chunk.toString()})response.on('end', () => {this.decodeInitializePlaylist(JSON.parse(body))})})request.on('error', e => {console.error(`problem with request ${e.message}`)})request.write(JSON.stringify(body))request.end()}decodeInitializePlaylist(response) {console.log(`#decodeInitializePlaylist`)console.log(response)const control_id = response['id']const djin_id = response['name']const externalUrl = response['external_urls']['spotify']const branch_path = `/events/${djin_id}`datastore.write(`/setup${branch_path}/spotify_id`, control_id)this.updateInitializedPlaylist(branch_path)}updateInitializedPlaylist(branch_path) {const name = datastore.read(`/setup${branch_path}/name`)const description = datastore.read(`/setup${branch_path}/description`)const options = {name,description: description + `\nCreated with Djinlist (www.djinlist.ca).`}this.changePlaylistDetails(branch_path, options, branch_path => {datastore.write(`/setup${branch_path}/initialized`, true)})}onPushPlaylistDetails(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valuedatastore.write(`/setup${pointer.branch_path}/details_synced`, false)this.debouncePushPlaylistDetails(value, pointer)}pushPlaylistDetails(value, pointer) {if (event.type !== '=') returnconst name = datastore.read(`/setup${pointer.branch_path}/name`)const description = datastore.read(`/setup${pointer.branch_path}/description`)const options = {}if (name) { Object.assign(options, { name }) }if (description) { Object.assign(options, { description }) }this.changePlaylistDetails(pointer.branch_path, options, branch_path => {datastore.write(`/setup${branch_path}/details_synced`, true)})}changePlaylistDetails(branch_path, options, callback) {const spotify_id = datastore.read(`/setup${branch_path}/spotify_id`)console.log(`#changePlaylistDetails ${branch_path}, ${spotify_id}`)console.log('options:', options)const request = https.request({method: 'PUT',hostname: 'api.spotify.com',path: `/v1/playlists/${spotify_id}`,headers: {'Authorization': `Bearer ${this.token()}`,'Content-Type': 'application/json'}},response => {response.on('end', () => callback(branch_path))})request.on('error', e => {console.error(`problem with request ${e.message}`)})request.write(JSON.stringify(options))request.end()}onTokenExpiry(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valueconst time = value - (+ new Date())console.log('#onTokenExpiry #{time}')if (this.refreshTokenInterval) {clearInterval(this.refreshTokenInterval)}if (time > 0) {this.refreshTokenInterval = setInterval(this.refreshToken.bind(this), time * 1000)} else {this.refreshToken()}}refreshToken() {const refreshToken = datastore.read(`/setup/admin/refresh_id`)console.log(`#refreshToken ${refresh_id}`)if (!refreshToken) { return }const body = {grant_type: 'refresh_token',refresh_token: refreshToken}request = https.request({method: 'POST',hostname: 'accounts.spotify.com',path: '/api/token',headers: {'Authorization': `Basic ZmUwMDk5M2ZmOTNlNDgyNzgwNGFmMTZlMWRlMzEyZGU6ODQ1NzQzNzhkMDg2NDQwZGI2MDczNmRiN2MxNzc1Mzg=`,'Content-Type': 'application/json'}},response => {if (![200, 201].includes(response.statusCode)) { return }let body = ''response.on('data', chunk => {body += chunk.toString()})response.on('end', () => {this.decodeTokenRefresh(JSON.parse(body))})})request.on('error', e => {console.log(`problem with request ${e.message}`)})request.write(JSON.stringify(body))request.end()}decodeRefreshToken(message) {console.log(`#decodeRefreshToken ${JSON.stringify(message)}`)const new_expiry = (+ new Date()) + message.expires_indatastore.write('/setup/admin/token', message.access_token)datastore.write('/setup/admin/expiry', new_expiry)}onPlaylistItemsQueued(value, pointer) {if (!value) returnconsole.log(`#onPlaylistItemsQueued ${value.length}`)const spotify_id = datastore.read(`/setup${pointer.branch_path}/spotify_id`)// const length = datastore.read(`/setup${pointer.branch_path}/spotify_id`)const request = https.request({method: 'PUT',hostname: 'api.spotify.com',path: `/v1/playlists/${spotify_id}/tracks`,headers: {'Authorization': `Bearer ${this.token()}`,'Content-Type': 'application/json'}})request.on('error', e => {console.log(`problem with request ${e.message}`)})const tracks = reduce(value || [], (acc, track) => {return concat(acc, [track[0]])}, [])request.write(JSON.stringify({uris: tracks}))request.end()}destroy() {}}const spotify = new Driverconst cleanup = () => {spotify.destroy()}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)export { spotify }
class Item {constructor() {console.log(`${this.toString()} #constructor`)datastore.subscribe('q/state/items/+/#', this.onStateQueued.bind(this))}toString() {return 'Model-Items'}onStateQueued(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valueconsole.log(`${this.toString()} #onStateQueued: ${pointer.path}, ${JSON.stringify(value)}`)this.accept(pointer, value)}accept(pointer, value, { force = false } = {}) {datastore.set(pointer, null, { silent: true })const dequeued_pointer = pointer.dequeue()datastore.set(dequeued_pointer.path, value, { force })}destroy() {}}const items = new Item()const cleanup = () => {items.destroy()}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)export { items }
import { forEach, reverse, sortBy, entries} from 'lodash-es'class Event {constructor() {console.log(`${this.toString()} #constructor`)datastore.subscribe('q/state/events/+/#', this.onStateQueued.bind(this))datastore.subscribe('q/setup/events/+/#', this.onSetupQueued.bind(this))this.ticker = setInterval(this.publishPlaylists.bind(this), 60 * 1000)}toString() {return 'Model-Events'}onStateQueued(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valueconsole.log(`${this.toString()} #onStateQueued: ${pointer.path}, ${JSON.stringify(value)}`)switch (pointer.branch_path.length) {case 4:if (pointer.branch_steps[2] == 'users') {this.onUserQueued(topic, pointer, value)}breakdefault:this.accept(pointer, value)break}}onSetupQueued(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valueconsole.log(`${this.toString()} #onSetupQueued: ${pointer.path}, ${JSON.stringify(value)}`)this.accept(pointer, value)}onUserQueued(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valueswitch (pointer.leaf) {case 'vote':this.accept(pointer, value)}}publishPlaylist(name, pointer) {const issueAt = datastore.read(pointer.path + '/issue_at')const currentTime = + new Date()console.log(`#publishPlaylist ${name} ${issueAt && (issueAt < currentTime)}`)if (!issueAt // &&// currentTime.getDay() == 5 ||// currentTime.getHours() == 20 ||// currentTime.getMinutes() == 0) {this.issuePlaylist(name, pointer)} else if (issueAt < currentTime) {this.issuePlaylist(name, pointer)// Determine the next time we need to update the playlistconst intervalDays = datastore.read(pointer.path + '/interval')const intervalSeconds = intervalDays * 24 * 60 * 60datastore.write(pointer.path + '/issue_at', issueAt + intervalSeconds)}}publishPlaylists() {const playlists = datastore.read('/setup/events/+/name')console.log(`#publishPlaylists`, playlists)forEach(playlists, (playlist, path) => {const pointer = new Pointer(path)this.publishPlaylist(playlist, pointer.slice(0, -1))})}tally(pointer) {const items = datastore.read(`/state${pointer.trunk_path}/users/+/items/+/vote`)const length = datastore.read(`/state${pointer.trunk_path}/users/+/items/+/vote`)const tally = {}forEach(items, (value, path) => {const id = path.split('/').slice(-2, -1)[0]if (tally[id]) {tally[id] += 1} else {tally[id] = 1}})const sorted_list = reverse(sortBy(entries(tally), entry => entry[1]))const current_list = datastore.read(`/state${pointer.trunk_path}/playlist`)let match = trueif (current_list) {for (var idx in current_list) {if (current_list[idx] != sorted_list[idx]) { match = false }}} else {match = false}if (match) returndatastore.write(`/q/state${pointer.trunk_path}/playlist`, sorted_list)}issuePlaylist(name, pointer) {console.log(`#issuePlaylist ${name} at ${pointer.path}`)const exists = datastore.read(`/setup${pointer.trunk_path}/spotify_id`)if (exists) {this.tally(pointer)} else {datastore.write(`/action${pointer.trunk_path}/start`, +new Date())}}accept(pointer, value, { force = false } = {}) {datastore.set(pointer, null, { silent: true })const dequeued_pointer = pointer.dequeue()datastore.set(dequeued_pointer.path, value, { force })}destroy() {console.log(`${this.toString()} #destroy`)clearInterval(this.ticker)}}const events = new Event()const cleanup = () => {events.destroy()}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)export { events }
class Admin {constructor() {console.log(`${this.toString()} #constructor`)datastore.subscribe('q/setup/admin/#', this.onSetupQueued.bind(this))}toString() {return 'Model-Admin'}onSetupQueued(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valueconsole.log(`${this.toString()} #onSetupQueued: ${pointer.path}, ${JSON.stringify(value)}`)this.accept(pointer, value)}accept(pointer, value, { force = false } = {}) {datastore.set(pointer, null, { silent: true })const dequeued_pointer = pointer.dequeue()datastore.set(dequeued_pointer.path, value, { force })}destroy() {}}const admin = new Admin()const cleanup = () => {admin.destroy()}process.on('SIGINT', cleanup)process.on('SIGTERM', cleanup)export { admin }
import _ from 'lodash'if (!global._) global._ = _import { Datastore, Pointer, Topic } from '@controlenvy/datastore'if (!global.datastore) global.datastore = new Datastore()if (!global.Pointer) global.Pointer = Pointerif (!global.Topic) global.Topic = Topicconst main = async () => {// configuration information goes heredatastore.set('/session/node_id', '952ede89-4c91-4df7-bdab-c6dda4257abb')import('./client.js')import('./server.js')}main()
import { Client } from '@djinlist/ipc/client'import { DATASTORE_PIPENAME } from '@djinlist/env'async function main() {const client = new Client('Channel', DATASTORE_PIPENAME)await client.connect()datastore.subscribe("state/#", (topic, { type, pointer }) => {if (type === '=') {client.write(pointer.path, pointer.tree?.value)}if (type === '-') {client.delete(pointer.path)}})datastore.subscribe("setup/#", (topic, { type, pointer }) => {if (type === '=') {client.write(pointer.path, pointer.tree?.value)}if (type === '-') {client.delete(pointer.path)}})client.subscribe("setup/#", (topic, path, value) => {console.log('write', {path, value})datastore.write(path, value)})client.subscribe("state/#", (topic, path, value) => {console.log('write', {path, value})datastore.write(path, value)})}main()
import { forEach } from 'lodash-es'class TopicTree {constructor() {Object.defineProperties(this, {_root: {value: this.createTreeNode()}})}createTreeNode() {const node = Object.create(null)Object.defineProperties(node, {_value: {writable: true}})return node}// expensive, call rarelyall(func = null, output = [], node = this._root) {if (node._topic && node._value && (!func || func(node))) {output.push([node._topic, node._value])}forEach(node, (child, key) => {if (!['_value', '_topic'].includes(key)) {this.all(func, output, child)}})return output}apply(func, node = this._root) {func(node)return forEach(node, (child, key) => {if (!['_value', '_topic'].includes(key)) {this.apply(func, child)}})}get(topic) {const steps = topic.split('/')let left = this._rootfor (const step of steps) {left = left[step]if (left == null) {left = this.createTreeNode()break}}return left}getWithDefault(topic, value) {const steps = topic.split('/')let node = this._rootfor (const step of steps) {if (node[step] == null) {node[step] = this.createTreeNode()}node = node[step]}if (node._value == null) {node._topic = topicnode._value = value}return node}add(topic, value) {const node = this.getWithDefault(topic)node._topic = topicnode._value = value}values(topic) {const steps = topic.split('/')return this._values(this._root, steps, 0, []).reverse()}_values(node, steps, pivot, values) {if (steps.length == pivot) {if (node._value != null) {values.push(node._value)}return values}const step = steps[pivot]if (node['#'] != null) {values.push(node['#']._value)}if (node['+'] != null) {values = this._values(node['+'], steps, pivot + 1, values)}if (node[step] != null) {values = this._values(node[step], steps, pivot + 1, values)}return values}entries(topic) {const steps = topic.split('/')return this._entries(this._root, steps, 0, []).reverse()}_entries(node, steps, pivot, entries) {if (steps.length == pivot) {if (node._value != null) {entries.push([node._topic, node._value])}if (node['*'] != null) {entries.push([node['*']._topic, node['*']._value])}return entries}const step = steps[pivot]if (node['#'] != null) {entries.push([node['#']._topic, node['#']._value])}if (node['+'] != null) {entries = this._entries(node['+'], steps, pivot + 1, entries)}if (node[step] != null) {entries = this._entries(node[step], steps, pivot + 1, entries)}return entries}}export { TopicTree }
import { Base } from './base.js'import { difference } from 'lodash-es'class Client extends Base {constructor() {super()this._sessions = {}// user datathis.blacklist(new Topic('state/users/+/auth/#'))this.blacklist(new Topic('state/users/+/auth/*'))this.blacklist(new Topic('state/users/+/topics'))datastore.subscribe('state/users/+/topics', this.approve.bind(this))datastore.subscribe('state/users/+/#', this.publish.bind(this))// item datathis.whitelist(new Topic('state/items/+/#'))// event datathis.whitelist(new Topic('setup/events/+/name'))this.whitelist(new Topic('setup/events/+/items/#'))this.whitelist(new Topic('setup/events/+/private'))this.blacklist(new Topic('state/events/+/pin'))datastore.subscribe('setup/events/+/pin', this.syndicate.bind(this))datastore.subscribe('setup/events/+/#', this.publish.bind(this))datastore.subscribe('state/events/+/#', this.publish.bind(this))this.initialize('/setup/events', '/pin', this.syndicate.bind(this))}approve(topic, event) {console.log(`#approve ${event.pointer.path}: ${event.type}`)if (event.type != '=') returnconsole.log(`#approve ${event.pointer.path}: ${event.pointer.tree?.value}`)let { pointer: { tree: { value } }} = eventconst user_id = topic.steps[2]const websocket = datastore.read(`/session/connections/${user_id}/ws`)console.log({user_id, test: datastore.read(`/session/connections`), added: difference(value, (this._sessions[user_id] || [])), removed: difference((this._sessions[user_id] || []), value)})if (websocket != null) {value ||= []const added = difference(value, (this._sessions[user_id] || []))console.log({added})for (const pattern of added) {const permissions = this._permissions.getWithDefault(pattern, [])console.log({permissions, websocket})if (permissions._value.includes(websocket)) { continue }permissions._value.push(websocket)console.log({permissions})}const removed = difference((this._sessions[user_id] || []), value)console.log({removed})for (const pattern of removed) {const permissions = this._permissions.getWithDefault(pattern, [])console.log({permissions, websocket})const found_index = permissions._value.indexOf(websocket)if (found_index === -1) { continue }permissions._value.splice(found_index, 1)console.log({permissions})}}this._sessions[user_id] = value}toString() {return 'Channel-Client'}}const client = new Client()export { client }
import { forEach, remove, concat, reduce, fromPairs, isPlainObject } from 'lodash-es'import { coppice } from '@controlenvy/datastore'import { TopicTree } from './topic_tree.js'import { authorization } from '../authorization.js'class Base {constructor() {Object.defineProperties(this, {_permissions: {value: new TopicTree},_subscribers: {value: new TopicTree}})}toString() {return 'Channel-Base'}whitelist(topic) {console.log(`${this.toString()} #whitelist ${topic.toString()}`)this.list(topic, 'whitelist')}blacklist(topic) {console.log(`${this.toString()} #blacklist ${topic.toString()}`)this.list(topic, 'blacklist')}dewhitelist(topic) {console.log(`${this.toString()} #dewhitelist ${topic.toString()}`)this.delist(topic, 'whitelist')}deblacklist(topic) {console.log(`${this.toString()} #deblacklist ${topic.toString()}`)this.delist(topic, 'blacklist')}list(topic, type) {const permissions = this._permissions.getWithDefault(topic.pattern, [])permissions._topic = topic.patternpermissions._value.push(type)}initialize(prefix, postfix, callback) {console.log(`${this.toString()} #initialize ${prefix}/+${postfix}`)const keys = datastore.get(prefix)?.keys() || []const callback_topic = `${prefix}/+${postfix}`forEach(keys, key => {const pointer = new Pointer(`${prefix}/${key}${postfix}`)const value = datastore.read(pointer)callback(callback_topic, pointer, value)})}delist(topic, type) {const permissions = this._permissions.get(topic.pattern)if (permissions == null || Object.keys(permissions).length === 0) { return }permissions.delete(type)}syndicate(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconst value = pointer?.tree?.valueconst syndicated_path = pointer.steps.slice(0, -1).concat('#').join('/')const private_path = '/' + pointer.steps.slice(0, -1).concat('private').join('/')console.log(`${this.toString()} #syndicate ${syndicated_path} ${value}`)if (!value) {this.whitelist(syndicated_path)datastore.delete(private_path)} else {this.dewhitelist(syndicated_path)datastore.write(private_path, true)}}publish(topic, event) {if (event.type !== '=') returnconst { pointer } = eventconsole.log(`${this.toString()} #publish ${topic} @ ${pointer.path}`)const permissions = this._permissions.entries(pointer.path.slice(1))console.log({permissions})let whitelisted = falsefor (var idx in permissions) {const _permitted = permissions[idx][1]if (_permitted.includes('blacklist')) { return }if (_permitted.includes('whitelist')) {whitelisted = true}}const permitted = concat(...Object.values(fromPairs(permissions)))console.log({permitted})remove(permitted,subscriber => ['whitelist', 'blacklist'].includes(subscriber))const subscribers = this._subscribers.entries(pointer.path.slice(1))if (subscribers.length == 0) { return }let parseif (whitelisted) {parse = ([_topic, subscribed]) => {forEach(subscribed, callbacks => {callbacks.forEach(callback => callback(_topic, pointer))})}} else {parse = ([_topic, subscribed]) => {forEach(subscribed, (callbacks, subscriber) => {if (permitted.includes(subscriber)) {callbacks.forEach(callback => callback(_topic, pointer))}})}}forEach(subscribers, parse)}createAuthorization(pointer, packet, subscriber) {const response = authorization.createAuthorization(pointer, packet)subscriber.send(JSON.stringify(response))}authorize(topic, packet, subscriber) {const response = authorization.authorize(topic, packet)subscriber.send(JSON.stringify(response))}prove(topic, packet, subscriber) {const response = authorization.prove(topic, packet)if (response.v.p === false) {console.log(`#prove FAIL ${topic.pattern} ${subscriber}`)return false}topic = topic.changeRoot('+')const permissions = this._permissions.getWithDefault(topic.pattern, [])permissions._value.push(subscriber.toString())console.log(`${this.toString()} #prove SUCCEED ${topic.pattern} ${subscriber}`, {branch: topic.branch})subscriber.send(JSON.stringify(response))console.log({test: `/state${topic.branch_path}/topics`})const topics = datastore.read(`/state${topic.branch_path}/topics`) || []for (const topic of topics) {const permissions = this._permissions.getWithDefault(topic, [])if (permissions._value.includes(subscriber.toString())) { continue }permissions._value.push(subscriber.toString())}}resume(topic, subscriber) {console.log(`${this.toString()} #topic ${topic.pattern} ${subscriber}`)const permissions = this._permissions.getWithDefault(topic.pattern, [])permissions._value.push(subscriber.toString())}isAuthorized(topic, subscriber) {const entries = this._permissions.entries(topic.dequeue().pattern)console.log({test1: topic.dequeue().pattern, test2: entries})if (!entries) {console.log(`${this.toString()} #isAuthorized FAIL ${topic.pattern}`)return false}let authorized = falsefor (var idx in entries) {const callbacks = entries[idx][1]console.log({callbacks})if (callbacks.includes('blacklist')) {console.log(`${this.toString()} #isAuthorized BLACKLIST ${entries[idx][0]} ${topic.pattern}`)return false}if (callbacks.includes('whitelist')) {console.log(`${this.toString()} #isAuthorized WHITELIST ${entries[idx][0]}`)authorized = true} else if (callbacks.includes(subscriber.toString())) {console.log(`${this.toString()} #isAuthorized APPROVED ${entries[idx][0]}`)authorized = true}}console.log(`${this.toString()} #isAuthorized ${authorized ? 'SUCCEED' : 'FAIL'} ${topic.pattern}`)return authorized}read(pointer, subscriber) {const data_coppice = {}if (pointer.isWildcard()) {console.log(`${this.toString()} #search ${pointer.path}`)return reduce(datastore.search(pointer.path), (result, value, path) => {if (this.isAuthorized(new Topic(path.slice(1)), subscriber)) {result[path] = value}return result}, {})} else {console.log(`${this.toString()} #read ${pointer.path}`)if (!this.isAuthorized(new Topic(pointer.steps), subscriber)) { return }const found = datastore.read(pointer.path)if (isPlainObject(found)) {coppice(found, pointer.path, data_coppice)} else {data_coppice[pointer.path] = found}}return data_coppice}write(pointer, value, subscriber) {if (!this.isAuthorized(new Topic(pointer.steps), subscriber)) { return }console.log(`${this.toString()} #write`, pointer.path, value)return datastore.write(pointer.path, value)}merge(pointer, value, subscriber) {if (!this.isAuthorized(new Topic(pointer.steps), subscriber)) { return }console.log(`${this.toString()} #merge`, pointer.path, value)return datastore.merge(pointer.path, value)}delete(pointer, subscriber) {if (!this.isAuthorized(new Topic(pointer.steps), subscriber)) { return }return datastore.delete(pointer.path)}subscribe(topic, subscriber, callback) {const subscribers = this._subscribers.getWithDefault(topic.pattern, {})._valueif (Array.isArray(subscribers[subscriber])) {subscribers[subscriber].push(callback)} else {subscribers[subscriber] = [callback]}}unsubscribe(subscriber) {console.log(`#unsubscribe ${subscriber}`)const removeSubscriber = ({ _value }) => {if (!_value) returndelete _value[subscriber]}this._subscribers.apply(removeSubscriber)const removePermission = ({ _value }) => {if (!_value) returnremove(_value, subscriber.toString())}this._permissions.apply(removePermission)}}export { Base }
import srp from "secure-remote-password/server.js"import crypto from "crypto"class serverAuthorization {createAuthorization(topic, packet) {// 0const salt = packet.sconst verifier = packet.vconst salt_topic = topic.replace('/#', '/auth/salt')const verifier_topic = topic.replace('/#', '/auth/verifier')const response = {o: 'a',c: 0,t: topic.pattern,v: true}console.log({salt_topic})response.v = !datastore.read('/' + salt_topic.pattern)if (response.v) {datastore.write('/' + salt_topic.pattern, salt)datastore.write('/' + verifier_topic.pattern, verifier)}return response // reject}authorize(topic, packet) {// 1const salt_topic = topic.replace('/#', '/auth/salt')const verifier_topic = topic.replace('/#', '/auth/verifier')const client_public_key_topic = topic.replace('/#', '/auth/client_public_key')const server_secret_key_topic = topic.replace('/#', '/auth/server_secret_key')console.log({topic, salt_topic, verifier_topic, client_public_key_topic, server_secret_key_topic})datastore.write('/' + client_public_key_topic.pattern, packet.k)const response = {o: 'a',c: 2,t: topic.pattern,v: {u: packet.u}}let salt = datastore.read('/' + salt_topic.pattern)let verifier = datastore.read('/' + verifier_topic.pattern)let ephemeralif (salt && verifier) {console.log(`#authorize() ${packet.u} found`)ephemeral = srp.generateEphemeral(verifier)datastore.write('/' + server_secret_key_topic.pattern, ephemeral.secret)response.v.k = ephemeral.publicresponse.v.s = salt} else {console.log(`#authorize() ${packet.u} not found`)salt = crypto.randomBytes(32).toString('hex')ephemeral = crypto.randomBytes(256).toString('hex')response.v.k = ephemeralresponse.v.s = salt}return response}prove(topic, packet) {// 3const salt_topic = topic.replace('/#', '/auth/salt')const verifier_topic = topic.replace('/#', '/auth/verifier')const client_public_key_topic = topic.replace('/#', '/auth/client_public_key')const server_secret_key_topic = topic.replace('/#', '/auth/server_secret_key')const session_key_topic = topic.replace('/#', '/auth/session_key')const server_secret_key = datastore.read('/' + server_secret_key_topic.pattern)const client_public_key = datastore.read('/' + client_public_key_topic.pattern)const salt = datastore.read('/' + salt_topic.pattern)const username = packet.uconst verifier = datastore.read('/' + verifier_topic.pattern)const proof = packet.pvar session, responsetry {session = srp.deriveSession(server_secret_key,client_public_key,salt,username,verifier,proof)datastore.write('/' + session_key_topic.pattern, session.key)response = {o: 'a',c: 4,t: topic.pattern,v: {u: packet.u,p: session.proof}}return response} catch (error) {response = {o: 'a',c: 4,t: topic.pattern,v: {u: packet.u,p: false}}return response}}}const authorization = new serverAuthorization()export { authorization }
{"name": "@djinlist/services_channel","version": "1.0.0","type": "module","license": "UNLICENSED","private": true,"main": "src/index.js","dependencies": {"ws": "^6.0.0","uuid": "^8.0.0","@djinlist/ipc": "1.0.0","@djinlist/env": "1.0.0"},"scripts": {"start": "node src/index.js"},"exports": {".": "./src/index.js"}}
{"state": {"users": {"8e97bb55-8b08-5eea-b6d8-3456ec25d301": {"auth": {"salt": "0b7a988a178f368841cd2f3c05d407d18de26dba6b78e3afb5967c71d9b146d2","verifier": "8f935c53fa9b18082f8a06060fa46064be5978adde30aafa89083ac5b25552685f58a33be35cc3f697dab8ceccba18320678ace3970e1a082c7b61117a6af4301a93f40e4c73f26302b10cfbb0fa7f2acb99df708c3eae77ad4f6ae62753692f91e2e76e48cae12a1206eced2feefea1acd0697697c3853248798f791ee30a35e842bea12bcca853ebee71a31a660a8eba5fbdc4b8a2859fabfb8adf8f97717371e8b415057726f78599ceda50413683cfcf3bf5885d8e70831d90c8afabb2d935b62970b1e4f77227a55d0916c65a6b91db329f1903fbbaf5d7fdd6d5ffa3332b50b36db5fc00fb186696adf16f23f0a6fcbcbdb73ce947326b660e0663078a","client_public_key": "1bc131d1a2cbba81d11874f03b2c3d55825aea5a709f36414503d6a53527429ba3011f6d49dafdfe0742d96f6f86068ce0ec119d588307a9932b9b27501253793af85c1814c849a3df55c3eaa18ab66fbc0bed71d22ba5a55d2c40f130da9db57f352d96162ab17a244a4901f4d54e3af2090e001b0b48a177780ece5b246c353ebb74579eb5367f4e924ec9fa17077e09a4edf55e6f57dcaa209b04c249751e56287e0d85b43eb514442fdd5af9fed087e946f8ec77982cef2b2d368eb5f084e7c859a5951dd660a2683befb75c653b4cae2e6dfaecd786b7fa81e5b1c397ac3e95d6118f2b2e31a977bc7cf6e8d97f90ada0eda0386bed56582d2c9b015f14","server_secret_key": "7d199e6b82b390f3ceed1c3ba32e4ec9a5893049ba0b5155f358e98c23984b71","session_key": "d53e81b5222afc85c3cb288df34f5bf1b1d1e6479be56ccb00e39dfce7adc74d"},"email": "thomas.p.cowan@gmail.com","name": "tpcowan","created_at": 1639825217687,"followed": ["/events/62854dc2-7d97-45d3-be03-f0bac69119f8"],"event_id": "/events/62854dc2-7d97-45d3-be03-f0bac69119f8","topics": ["+/events/56bf15ad-0b26-4fd9-b838-aebe8d73b244/#","+/events/a7313474-90e4-4938-b59d-6eb7a59a0f68/#"],"owned": ["/events/56bf15ad-0b26-4fd9-b838-aebe8d73b244","/events/a7313474-90e4-4938-b59d-6eb7a59a0f68"]},"919340b1-1217-548e-914b-a876f726b62a": {"auth": {"client_public_key": "a9af1bcbea8f295550dda1a5bf491e3e6e586cc6b9297ec629911fef567f19c2e82dbbab07e9cf5e0de0106a29fe2a3e05aecde84e0d326894e018096f99a8a0869a51146fa253f79e4859b7e93ea37fd3cc589a7ef0f3129e087d190627a7b9e63fbea7a233232651195943f02fa2d86244c220ce457d65ee9985edacb9126c52c92e16dea1064d0f1a2b6b7d0c5b1f49ea4872cc0d0def94198b11731d4e2296368a5ab35b587ca33bd5a19de124c6f2f6a3511589f39e98ecd1e8b3b2f283496aae91ce6fc440117e8cdd492d09796c951c2819faa3934ea6e4cf1585ccf5b54947a62adf7ecbede8d77d5bfbaeec77618fbeab673fafa72933158480efe6"}}}},"setup": {"events": {"75c6cfd0-139a-4a33-8826-9c284645f1ae": {"name": "Calgary Weekly List"},"62854dc2-7d97-45d3-be03-f0bac69119f8": {"name": "Toronto Weekly List"},"fe71a1ee-6e64-4d4f-8a03-7b091d93c823": {"name": "Belgium Weekly List"},"56bf15ad-0b26-4fd9-b838-aebe8d73b244": {"private": true,"owner": "8e97bb55-8b08-5eea-b6d8-3456ec25d301","admin": ["8e97bb55-8b08-5eea-b6d8-3456ec25d301"],"users": ["8e97bb55-8b08-5eea-b6d8-3456ec25d301"],"name": "My Birthday"},"a7313474-90e4-4938-b59d-6eb7a59a0f68": {"private": true,"owner": "8e97bb55-8b08-5eea-b6d8-3456ec25d301","admin": ["8e97bb55-8b08-5eea-b6d8-3456ec25d301"],"users": ["8e97bb55-8b08-5eea-b6d8-3456ec25d301"]}}}}
#!/usr/bin/env bash. $BIN_DIR/_lib.shnode --experimental-modules --experimental-json-modules --es-module-specifier-resolution=node src/index.js
.git*.lognode_modulesapp/nginx.conf
#!/usr/bin/env bash. $BIN_DIR/_lib.shrsync --progress -Pavuz --exclude-from="$WORKING_BIN_DIR/rsync-deploy.ignore" -e "ssh -i $HOME/.ssh/id_rsa_corda_digital_ocean" "${MONO_DIR}/." "tpcowan@processor.djinmusic.ca:/home/tpcowan/djinmusic"rsync --progress -Pavuz -e "ssh -i $HOME/.ssh/id_rsa_corda_digital_ocean" $WORKING_BIN_DIR/deploy-remote.sh "tpcowan@processor.djinmusic.ca:/home/tpcowan/deploy-remote.sh"ssh -F $HOME/.ssh/id_rsa_corda_digital_ocean tpcowan@processor.djinmusic.ca "sh /home/tpcowan/deploy-remote.sh"
#!/usr/bin/env bashif [[ "$(uname -s)" == "Darwin" ]]; thenecho "Don't run this on your local computer!"exit 1fiecho "[remote] Updating processor"cd djinmusicpnpm install -rcd ..echo "[remote] Installed"
#!/bin/zshautoenv_source_parentautostash WORKING_DIR=$(dirname ${0})autostash WORKING_BIN_DIR="${WORKING_DIR}/.bin"autostash alias start="${WORKING_BIN_DIR}/start.sh"
4620c8a2ebdb3dca9a2cc9e001b63408a16232fba35dc5ec72d70ea48d2bfcdf:b73f475d060617a870cd42d5496c5ee86fbccdd8efaace3e81079411872c2564:42384237314242382d453531392d343241352d384634422d464238414442373646463546:66643731666435382d373332332d346435392d623164302d383464396231666232386661
import net from 'net'import varint from 'varint'class Client {constructor(client, datastore) {this.client = clientthis.datastore = datastorethis._buffer = Buffer.alloc(0)this.onData = this.onData.bind(this)this.onMessage = this.onMessage.bind(this)this.publish = this.publish.bind(this)}start() {this.client.on('data', this.onData)this.client.on('message', this.onMessage)}onData(data) {console.log({data})this._buffer = Buffer.concat([this._buffer, data])console.log({buffer: this._buffer})this._buffer = this.parseEnvelope(this._buffer)}onMessage(message) {try {message = JSON.parse(message)console.log({message})if (!message.o) throw `Unkown message operation`switch (message.o) {case 'm': {this.datastore.merge(message.p, message.v)} breakcase 'w': {this.datastore.write(message.p, message.v)} breakcase 'ps': {this.datastore.push(message.p, message.v)if (message.i) {this.read(message)}} breakcase 'pl': {this.datastore.pull(message.p, message.v)if (message.i) {this.read(message)}} breakcase 'd': {this.datastore.delete(pointer.path)} breakcase 's': {this.datastore.subscribe(message.t, this.publish, { immediate: false })if (message.i) {this.search(message)}} breakcase 'se': {this.search(message)} breakcase 'r': {this.read(message)} breakcase 'u': {this.datastore.unsubscribe(message.t, publish)break}}} catch (error) {console.log(`Erroring parsing message: ${message}`)console.log(error)}}send(json) {console.log('sending', { json })this.client.write(Buffer.concat([Buffer.from(varint.encode(json.length)), Buffer.from(json, 'utf-8')]))}read(message) {const value = this.datastore.read(message.p)const response = {o: 'r' ,v: value,i: message.i}this.send(JSON.stringify(response))}search(message) {const results = this.datastore.search(message.t)const response = {o: message.o,i: message.i,t: message.t,v: results}this.send(JSON.stringify(response))}publish(topic, { type, pointer}) {const message = {o: 'p',t: topic.path,p: pointer.path,v: pointer.tree?.value}this.send(JSON.stringify(message))}parseEnvelope(buffer) {while (true) {if (buffer.length == 0) return buffertry {const message_length = varint.decode(buffer)const total_length = varint.decode.bytes + message_lengthif (buffer.length >= total_length) {const message = buffer.slice(varint.decode.bytes, total_length)this.client.emit('message', message)buffer = buffer.slice(total_length)} else {return buffer}} catch (error) {return Buffer.alloc(0)}}}}class Server {constructor(path, datastore) {this.path = paththis.datastore = datastorethis.cleanup = this.cleanup.bind(this)}async start() {/* IPC Sockets */this.server = net.createServer(function(client) {const instance = new Client(client, this.datastore)instance.start()}.bind(this))this.server.listen(this.path)}cleanup() {console.log('\rShutting down ipc server') // eslint-disable-line no-consolethis.server.close()}}export { Server }
const PubSub = (Base) => class extends Base {async subscribe(topic, callback, { immediate = true } = {}) {console.log('Client subscribe', topic)const callbacks = this.subscriptions[topic]if (Array.isArray(callbacks)) {this.subscriptions[topic].push(callback)} else {this.subscriptions[topic] = [callback]const message = {o: 's',t: topic,i: immediate}this.send(message)}}unsubscribe(topic, callback) {const callbacks = this.subscriptions[topic]if (typeof callbacks === 'undefined') returnif (callbacks.incluseds(callback)) {this.subscriptions[topic] = callbacks.splice(callbacks.indexOf(callback), 1)}if (this.subscriptions[topic].length === 0) {delete this.subscriptions[topic]const message = {o: 'u',t: topic}this.send(message)}}}export { PubSub }
import net from 'net'import varint from 'varint'import { Convenience } from './convenience.js'import { Api } from './api.js'import { PubSub } from './pubsub.js'const RECONNECT_TIME = 5class IPC {constructor(name, path) {this.name = namethis.path = paththis.destroyed = falsethis.waiters = {}this.subscriptions = {}}async awaitResponse(message) {this.send(message)return new Promise((resolve, reject) => {this.waiters[message.i] = (response) => resolve(response)setTimeout(reject, 5000)})}parseEnvelope(buffer) {while (true) {if (buffer.length == 0) return buffertry {const message_length = varint.decode(buffer)console.log(message_length)const total_length = varint.decode.bytes + message_lengthconsole.log(total_length)if (buffer.length >= total_length) {const message = buffer.slice(varint.decode.bytes, total_length)const json = JSON.parse(message.toString('utf-8'))this.socket.emit('message', json)buffer = buffer.slice(total_length)} else {return buffer}} catch (error) {console.log("E", error)return Buffer.alloc(0)}}}/** Connect*/async delayConnect() {if (this.connect_timeout) returnconsole.log(`Reconnecting in ${RECONNECT_TIME}s.`)return new Promise((resolve) => {this.connect_timeout = setTimeout(() => {this.connect_timeout = nullresolve(this.connect())}, RECONNECT_TIME * 1000)})}async connect() {if (this.socket && (this.socket.connecting || this.socket.connected)) returntry {console.log(`Connect() ${this.path}`)await new Promise(function(resolve, reject) {const connectTimeout = setTimeout(() => {reject(new Error("Connection Timeout"))}, 5000)this.socket = net.createConnection(this.path)this.socket.once('connect', () => {console.log('Connected')clearTimeout(connectTimeout)this.socket.connected = trueresolve()})this.socket.on('error', error => {this.connecting = falseconsole.log(`Error connecting to socketc: ${error}`)clearTimeout(connectTimeout)reject(error)})}.bind(this))this.socket.data_buffer = new Buffer.alloc(0)this.socket.on('data', (data) => {console.log('data', {data})this.socket.data_buffer = Buffer.concat([this.socket.data_buffer, data])this.socket.data_buffer = this.parseEnvelope(this.socket.data_buffer)})this.socket.on('message', (message) => {console.log('message', { message })switch (message.o) {case 'ps':case 'pl':case 'se':case 'r': {const waiter = this.waiters[message.i]if (typeof waiter === 'function') {waiter(message)delete this.waiters[message.i]}} breakcase 'p': {const subscriptions = this.subscriptions[message.i]if (Array.isArray(subscriptions)) {for (const subscription of subscriptions) {subscription(message.t, message.p, message.v)}}}case 's': {const subscriptions = this.subscriptions[message.t]console.log({subscriptions})if (Array.isArray(subscriptions)) {for (const path in message.v) {const value = message.v[path]for (const subscription of subscriptions) {console.log(message.t, path, value)subscription(message.t, path, value)}}}}}})this.socket.on('close', erred => {console.log('Closed')this.socket.connected = falsetry {this.socket.destroy()} catch (e) {console.log(`Error destroying socket:\n${e.stack}`)}if (erred) {console.log('Socket closed due to an error')}this.delayConnect()})return} catch (e) {this.connecting = falseconsole.log(`Error connecting to socket: ${e}`)return await this.delayConnect()}}/** Send*/send(message) {const payload = JSON.stringify(message)console.log(`#send() ${payload}`)const buffer = Buffer.concat([Buffer.from(varint.encode(payload.length)), Buffer.from(payload)])try {this.socket && this.socket.write(buffer)} catch (e) {console.log(e)}}/** Cleanup*/cleanup() {this.socket.destroy()this.destroyed = true}}const Client = Convenience(PubSub(Api(IPC)))export { Client }
import { v4 as uuid } from 'uuid'const Convenience = (Base) => class extends Base {async push(path, { immediate = true } = {}) {// console.log(`READ (R): ${pointer.path}`)const message = {o: 'ps',p: path}if (immediate) {message.i = uuid()const response = await this.awaitResponse(message)return response.v} else {this.send(message)return Promise.resolve()}}async pull(path, { immediate = true } = {}) {// console.log(`READ (R): ${pointer.path}`)const message = {o: 'pl',p: path}if (immediate) {message.i = uuid()const response = await this.awaitResponse(message)return response.v} else {this.send(message)return Promise.resolve()}}}export { Convenience }
import { v4 as uuid } from 'uuid'const Api = (Base) => class extends Base {async merge(path, value) {// console.log(`MERGE: ${pointer.path}`)const message = {o: 'm',p: path,v: value}this.send(message)}async write(path, value) {// console.log(`WRITE: ${pointer.path}`)const message = {o: 'w',p: path,v: value}this.send(message)}async delete(path) {// console.log(`DESTROY: ${pointer.path}`)const message = {o: 'd',p: path}this.send(message)}async read(path) {// console.log(`READ (R): ${pointer.path}`)const message = {o: 'r',p: path,i: uuid()}const response = await this.awaitResponse(message)console.log({response})return response.v}async search(pointer) {// console.log(`READ (R): ${pointer.path}`)const message = {o: 'se',t: topic,i: uuid()}const response = await this.awaitResponse(message)return response.v}}export { Api }
{"name": "@djinlist/ipc","version": "1.0.0","type": "module","license": "UNLICENSED","private": true,"main": "src/index.js","exports": {"./server": "./src/server/index.js","./client": "./src/client/index.js"},"dependencies": {"varint": "^6.0.0","uuid": "^8.0.0"}}
import path from 'path'import url from 'url'import { access, mkdir, constants } from 'fs'const __filename = url.fileURLToPath(import.meta.url)const __dirname = path.dirname(__filename)const IPC_DIRNAME = path.join(__dirname, '..', '..', '..', 'env', 'tmp', 'ipc')await new Promise((resolve) => {access(path.join(__dirname, '..', '..', '..', 'env', 'tmp', 'ipc'), constants.F_OK, (err) => {if (err) {mkdir(path.join(__dirname, '..', '..', '..', 'env', 'tmp', 'ipc'), { recursive: true }, (err) => {if (typeof err === 'undefined') {resolve()}})} else {resolve()}})})const DATASTORE_PIPENAME = path.join(IPC_DIRNAME, 'datastore.ipc')export { IPC_DIRNAME, DATASTORE_PIPENAME }
{"name": "@djinlist/env","version": "1.0.0","type": "module","license": "UNLICENSED","private": true,"main": "src/index.js","dependencies": {"lodash": "^4.17.15"}}
module.exports = {apps : [{name : "frontend",cwd: './app/djiny',script : "pnpm",args: "run dev",env_production: {NODE_ENV: "production"},env_development: {NODE_ENV: "development"}},{name : "datastore",cwd: './services/datastore_ipc_server',script : "./src/index.js",},{name : "channel",cwd: './services/channel_ws_server',script : "./src/index.js",},{name : "user_portal",cwd: './services/user_portal_http_server',script : "./src/index.js",}]}
digraph{subgraph cluster10604544 {label="Page 10604544, rc 0 112";color=black;n_10604544_0[label="0: Inode(HT7GA5KVLSRV6):global.css -> Inode(SVLEQ7VDSJCXU)"];n_10604544_0->n_10604544_1[color="blue"];n_10604544_1[label="1: Inode(QIVCPWRQJNZS2):albums.svelte -> Inode(66XEAAZWKFAIE)"];n_10604544_1->n_10604544_2[color="blue"];n_10604544_2[label="2: Inode(3DJEOGW5L5HCE): -> Inode(3DJEOGW5L5HCE)"];}n_10604544_0->n_10608640_0[color="ForestGreen"];n_10604544_0->n_9728000_0[color="red"];n_10604544_1->n_9756672_0[color="red"];n_10604544_2->n_9805824_0[color="red"];subgraph cluster10608640 {label="Page 10608640, rc 0 2732";color=black;n_10608640_0[label="0: Inode(AAAAAAAAAAAAA):.autoenv.zsh -> Inode(Q2IFVLZRC36F4)"];n_10608640_0->n_10608640_1[color="blue"];n_10608640_1[label="1: Inode(AAAAAAAAAAAAA):.bin -> Inode(WQMKGUAN3HGPM)"];n_10608640_1->n_10608640_2[color="blue"];n_10608640_2[label="2: Inode(AAAAAAAAAAAAA):.ignore -> Inode(6OEXOKTZKXN2M)"];n_10608640_2->n_10608640_3[color="blue"];n_10608640_3[label="3: Inode(AAAAAAAAAAAAA):.nginx -> Inode(PZD6LOZIK35ES)"];n_10608640_3->n_10608640_4[color="blue"];n_10608640_4[label="4: Inode(AAAAAAAAAAAAA):.node-version -> Inode(VHCKXZRYKYWPO)"];n_10608640_4->n_10608640_5[color="blue"];n_10608640_5[label="5: Inode(AAAAAAAAAAAAA):.npmrc -> Inode(QG2K5UAACTJVM)"];n_10608640_5->n_10608640_6[color="blue"];n_10608640_6[label="6: Inode(AAAAAAAAAAAAA):.prettierignore -> Inode(DAAA7FS2JR7VY)"];n_10608640_6->n_10608640_7[color="blue"];n_10608640_7[label="7: Inode(AAAAAAAAAAAAA):.prettierrc -> Inode(BHKWO2MUFHJJI)"];n_10608640_7->n_10608640_8[color="blue"];n_10608640_8[label="8: Inode(AAAAAAAAAAAAA):.stylelintignore -> Inode(K3N24GAKRRFAS)"];n_10608640_8->n_10608640_9[color="blue"];n_10608640_9[label="9: Inode(AAAAAAAAAAAAA):.stylelintrc -> Inode(ZQYD3PLZGJFS2)"];n_10608640_9->n_10608640_10[color="blue"];n_10608640_10[label="10: Inode(AAAAAAAAAAAAA):app -> Inode(65TLXQ7KZYYJ4)"];n_10608640_10->n_10608640_11[color="blue"];n_10608640_11[label="11: Inode(AAAAAAAAAAAAA):ecosystem.config.js -> Inode(BH3N27R4V72E2)"];n_10608640_11->n_10608640_12[color="blue"];n_10608640_12[label="12: Inode(AAAAAAAAAAAAA):lib -> Inode(BLFA4SONQC4RC)"];n_10608640_12->n_10608640_13[color="blue"];n_10608640_13[label="13: Inode(AAAAAAAAAAAAA):package.json -> Inode(2RCNJFCVEVJD6)"];n_10608640_13->n_10608640_14[color="blue"];n_10608640_14[label="14: Inode(AAAAAAAAAAAAA):pnpm-lock.yaml -> Inode(VXCTX4J7VNL4Y)"];n_10608640_14->n_10608640_15[color="blue"];n_10608640_15[label="15: Inode(AAAAAAAAAAAAA):pnpm-workspace.yaml -> Inode(FD4VNPTDANJZ2)"];n_10608640_15->n_10608640_16[color="blue"];n_10608640_16[label="16: Inode(AAAAAAAAAAAAA):proxy.conf -> Inode(3EIM3JNV6WSTS)"];n_10608640_16->n_10608640_17[color="blue"];n_10608640_17[label="17: Inode(AAAAAAAAAAAAA):redirect.conf -> Inode(XNV3NBFDAM4HA)"];n_10608640_17->n_10608640_18[color="blue"];n_10608640_18[label="18: Inode(AAAAAAAAAAAAA):services -> Inode(CFCFTRHVRS3NE)"];n_10608640_18->n_10608640_19[color="blue"];n_10608640_19[label="19: Inode(AYO4INBMW4MC6): -> Inode(AYO4INBMW4MC6)"];n_10608640_19->n_10608640_20[color="blue"];n_10608640_20[label="20: Inode(AYO4INBMW4MC6):index.svelte -> Inode(XVC4OAALSIDVE)"];n_10608640_20->n_10608640_21[color="blue"];n_10608640_21[label="21: Inode(AYO4INBMW4MC6):wave.svelte -> Inode(4LQVCAQ57DFRM)"];n_10608640_21->n_10608640_22[color="blue"];n_10608640_22[label="22: Inode(BC72NKTJI6CHK): -> Inode(BC72NKTJI6CHK)"];n_10608640_22->n_10608640_23[color="blue"];n_10608640_23[label="23: Inode(BC72NKTJI6CHK):_error.svelte -> Inode(WRZLXXZBLEGN6)"];n_10608640_23->n_10608640_24[color="blue"];n_10608640_24[label="24: Inode(BC72NKTJI6CHK):_global.svelte -> Inode(DQOV3W2AJK2WM)"];n_10608640_24->n_10608640_25[color="blue"];n_10608640_25[label="25: Inode(BC72NKTJI6CHK):_layout.svelte -> Inode(TUHKQKMA5TYD2)"];n_10608640_25->n_10608640_26[color="blue"];n_10608640_26[label="26: Inode(BC72NKTJI6CHK):_oauth -> Inode(LFVZUHPU5CAKS)"];n_10608640_26->n_10608640_27[color="blue"];n_10608640_27[label="27: Inode(BC72NKTJI6CHK):event.svelte -> Inode(SHOPTS5SEAO4Q)"];n_10608640_27->n_10608640_28[color="blue"];n_10608640_28[label="28: Inode(BC72NKTJI6CHK):index.svelte -> Inode(2CZ5FAIFDGJW2)"];n_10608640_28->n_10608640_29[color="blue"];n_10608640_29[label="29: Inode(BLFA4SONQC4RC): -> Inode(BLFA4SONQC4RC)"];n_10608640_29->n_10608640_30[color="blue"];n_10608640_30[label="30: Inode(BLFA4SONQC4RC):datastore -> Inode(TACHSLXLDQETM)"];n_10608640_30->n_10608640_31[color="blue"];n_10608640_31[label="31: Inode(CFCFTRHVRS3NE): -> Inode(CFCFTRHVRS3NE)"];n_10608640_31->n_10608640_32[color="blue"];n_10608640_32[label="32: Inode(CFCFTRHVRS3NE):pnpm-lock.yaml -> Inode(6YHCEN6RVEXUE)"];n_10608640_32->n_10608640_33[color="blue"];n_10608640_33[label="33: Inode(CFCFTRHVRS3NE):pnpm-workspace.yaml -> Inode(QSQHBRGBBX2GU)"];n_10608640_33->n_10608640_34[color="blue"];n_10608640_34[label="34: Inode(COXOPYDYKF3XG): -> Inode(COXOPYDYKF3XG)"];n_10608640_34->n_10608640_35[color="blue"];n_10608640_35[label="35: Inode(COXOPYDYKF3XG):api.js.dep -> Inode(YNCX42XQFZ65G)"];n_10608640_35->n_10608640_36[color="blue"];n_10608640_36[label="36: Inode(COXOPYDYKF3XG):app.html -> Inode(UAUPZ7OL2HPSW)"];n_10608640_36->n_10608640_37[color="blue"];n_10608640_37[label="37: Inode(COXOPYDYKF3XG):client.js.dep -> Inode(NSGYKJ6WNYTWC)"];n_10608640_37->n_10608640_38[color="blue"];n_10608640_38[label="38: Inode(COXOPYDYKF3XG):lib -> Inode(7GVTKXM3HLWNQ)"];n_10608640_38->n_10608640_39[color="blue"];n_10608640_39[label="39: Inode(COXOPYDYKF3XG):routes -> Inode(SPVUATTTWOMBC)"];n_10608640_39->n_10608640_40[color="blue"];n_10608640_40[label="40: Inode(CVXDRUYJN25PO): -> Inode(CVXDRUYJN25PO)"];n_10608640_40->n_10608640_41[color="blue"];n_10608640_41[label="41: Inode(CVXDRUYJN25PO):album.svelte -> Inode(Q3LJU42ZMZW5U)"];n_10608640_41->n_10608640_42[color="blue"];n_10608640_42[label="42: Inode(CVXDRUYJN25PO):artist.svelte -> Inode(NGCHD7KEOYI4Q)"];n_10608640_42->n_10608640_43[color="blue"];n_10608640_43[label="43: Inode(CVXDRUYJN25PO):playlist.svelte -> Inode(VFGER7YRVYQLQ)"];n_10608640_43->n_10608640_44[color="blue"];n_10608640_44[label="44: Inode(CVXDRUYJN25PO):track.svelte -> Inode(QU6MSJXO7RQ2A)"];n_10608640_44->n_10608640_45[color="blue"];n_10608640_45[label="45: Inode(C3ZNORJPW55RC): -> Inode(C3ZNORJPW55RC)"];n_10608640_45->n_10608640_46[color="blue"];n_10608640_46[label="46: Inode(C3ZNORJPW55RC):empty_object.js -> Inode(6R52MGAG4BSWO)"];n_10608640_46->n_10608640_47[color="blue"];n_10608640_47[label="47: Inode(DD3J6NXBHNLGU): -> Inode(DD3J6NXBHNLGU)"];n_10608640_47->n_10608640_48[color="blue"];n_10608640_48[label="48: Inode(DD3J6NXBHNLGU):datastore -> Inode(6TMTDO45E3YQY)"];n_10608640_48->n_10608640_49[color="blue"];n_10608640_49[label="49: Inode(DD3J6NXBHNLGU):datastore.js -> Inode(TZ6UPCSH3BSLI)"];n_10608640_49->n_10608640_50[color="blue"];n_10608640_50[label="50: Inode(ESKHFJ2QX7P5C): -> Inode(ESKHFJ2QX7P5C)"];n_10608640_50->n_10608640_51[color="blue"];n_10608640_51[label="51: Inode(ESKHFJ2QX7P5C):index.js -> Inode(IG3HCGWHCKBNK)"];n_10608640_51->n_10608640_52[color="blue"];n_10608640_52[label="52: Inode(EWGIKU7N4TFWO): -> Inode(EWGIKU7N4TFWO)"];n_10608640_52->n_10608640_53[color="blue"];n_10608640_53[label="53: Inode(EWGIKU7N4TFWO):controlenvy_pointer.js -> Inode(KO67Z3WXJTYGW)"];n_10608640_53->n_10608640_54[color="blue"];n_10608640_54[label="54: Inode(EWGIKU7N4TFWO):controlenvy_topic.js -> Inode(QGCRDEDFXUZHM)"];n_10608640_54->n_10608640_55[color="blue"];n_10608640_55[label="55: Inode(EWGIKU7N4TFWO):pointer.js -> Inode(K3TC36J7SYF7U)"];n_10608640_55->n_10608640_56[color="blue"];n_10608640_56[label="56: Inode(EWGIKU7N4TFWO):topic.js -> Inode(XQJT6RBLBSTAK)"];n_10608640_56->n_10608640_57[color="blue"];n_10608640_57[label="57: Inode(FACHIVHPFYCQA): -> Inode(FACHIVHPFYCQA)"];n_10608640_57->n_10608640_58[color="blue"];n_10608640_58[label="58: Inode(FACHIVHPFYCQA):followed.svelte -> Inode(QCUQSEAR7KL2U)"];n_10608640_58->n_10608640_59[color="blue"];n_10608640_59[label="59: Inode(FACHIVHPFYCQA):owned.svelte -> Inode(VXLRVVS37U56C)"];n_10608640_59->n_10608640_60[color="blue"];n_10608640_60[label="60: Inode(FNO44B3I6OJIG): -> Inode(FNO44B3I6OJIG)"];n_10608640_60->n_10608640_61[color="blue"];n_10608640_61[label="61: Inode(FNO44B3I6OJIG):example.json -> Inode(GX2BLCQQ3Q72K)"];n_10608640_61->n_10608640_62[color="blue"];n_10608640_62[label="62: Inode(FPEFGAHOE333Y): -> Inode(FPEFGAHOE333Y)"];n_10608640_62->n_10608640_63[color="blue"];n_10608640_63[label="63: Inode(FWTL334DDZDFG): -> Inode(FWTL334DDZDFG)"];n_10608640_63->n_10608640_64[color="blue"];n_10608640_64[label="64: Inode(FWTL334DDZDFG):index.js -> Inode(IBVEXPVMM7YRW)"];n_10608640_64->n_10608640_65[color="blue"];n_10608640_65[label="65: Inode(GHPSHGSWVRTC2): -> Inode(GHPSHGSWVRTC2)"];n_10608640_65->n_10608640_66[color="blue"];n_10608640_66[label="66: Inode(GHPSHGSWVRTC2):.autoenv.zsh -> Inode(QPSRGSGOLZTPI)"];n_10608640_66->n_10608640_67[color="blue"];n_10608640_67[label="67: Inode(GHPSHGSWVRTC2):.bin -> Inode(WYD4M6RXKB7OC)"];n_10608640_67->n_10608640_68[color="blue"];n_10608640_68[label="68: Inode(GHPSHGSWVRTC2):.gitignore -> Inode(UVTRBKEMBJXYK)"];n_10608640_68->n_10608640_69[color="blue"];n_10608640_69[label="69: Inode(GHPSHGSWVRTC2):README.md -> Inode(UNZETESGWNSRC)"];n_10608640_69->n_10608640_70[color="blue"];n_10608640_70[label="70: Inode(GHPSHGSWVRTC2):cypress -> Inode(7BD75A5OV3RRU)"];n_10608640_70->n_10608640_71[color="blue"];n_10608640_71[label="71: Inode(GHPSHGSWVRTC2):package.json -> Inode(UQDJX74DAJPIG)"];n_10608640_71->n_10608640_72[color="blue"];n_10608640_72[label="72: Inode(GHPSHGSWVRTC2):rollup.config.js -> Inode(G5AGB5VNI7ZS2)"];n_10608640_72->n_10608640_73[color="blue"];n_10608640_73[label="73: Inode(GHPSHGSWVRTC2):src -> Inode(HT7GA5KVLSRV6)"];n_10608640_73->n_10608640_74[color="blue"];n_10608640_74[label="74: Inode(GHPSHGSWVRTC2):static -> Inode(VSXBCX4TPIMJ4)"];n_10608640_74->n_10608640_75[color="blue"];n_10608640_75[label="75: Inode(GN4XK57WEBIZ2): -> Inode(GN4XK57WEBIZ2)"];n_10608640_75->n_10608640_76[color="blue"];n_10608640_76[label="76: Inode(GN4XK57WEBIZ2):album.svelte -> Inode(DQ3OVUM3IUDYU)"];n_10608640_76->n_10608640_77[color="blue"];n_10608640_77[label="77: Inode(GN4XK57WEBIZ2):artist.svelte -> Inode(J6J2YZLLNIEBI)"];n_10608640_77->n_10608640_78[color="blue"];n_10608640_78[label="78: Inode(GN4XK57WEBIZ2):playlist.svelte -> Inode(BKTJTGA64SXDQ)"];n_10608640_78->n_10608640_79[color="blue"];n_10608640_79[label="79: Inode(GN4XK57WEBIZ2):track.svelte -> Inode(UHI7XCDD3VDI4)"];n_10608640_79->n_10608640_80[color="blue"];n_10608640_80[label="80: Inode(HDJNI6MNYEHJQ): -> Inode(HDJNI6MNYEHJQ)"];n_10608640_80->n_10608640_81[color="blue"];n_10608640_81[label="81: Inode(HDJNI6MNYEHJQ):global.css -> Inode(XXXHI67UNHQVK)"];n_10608640_81->n_10608640_82[color="blue"];n_10608640_82[label="82: Inode(HT7GA5KVLSRV6): -> Inode(HT7GA5KVLSRV6)"];n_10608640_82->n_10608640_83[color="blue"];n_10608640_83[label="83: Inode(HT7GA5KVLSRV6):bootstrap.js -> Inode(SFB6HENNG2M5A)"];n_10608640_83->n_10608640_84[color="blue"];n_10608640_84[label="84: Inode(HT7GA5KVLSRV6):client.js -> Inode(HZVGFJJV77CPQ)"];n_10608640_84->n_10608640_85[color="blue"];n_10608640_85[label="85: Inode(HT7GA5KVLSRV6):components -> Inode(5ODKHB7CLODEU)"];}subgraph cluster9728000 {label="Page 9728000, rc 0 2270";color=black;n_9728000_0[label="0: Inode(HT7GA5KVLSRV6):lib -> Inode(QZDUDJDCZJFQC)"];n_9728000_0->n_9728000_1[color="blue"];n_9728000_1[label="1: Inode(HT7GA5KVLSRV6):routes -> Inode(BC72NKTJI6CHK)"];n_9728000_1->n_9728000_2[color="blue"];n_9728000_2[label="2: Inode(HT7GA5KVLSRV6):server.js -> Inode(4LRQK6WXFX5V4)"];n_9728000_2->n_9728000_3[color="blue"];n_9728000_3[label="3: Inode(HT7GA5KVLSRV6):service-worker.js -> Inode(PBOADSJ5HZP4I)"];n_9728000_3->n_9728000_4[color="blue"];n_9728000_4[label="4: Inode(HT7GA5KVLSRV6):template.html -> Inode(NK4AJB5QYKL6I)"];n_9728000_4->n_9728000_5[color="blue"];n_9728000_5[label="5: Inode(IIJXKYQFPEGVO): -> Inode(IIJXKYQFPEGVO)"];n_9728000_5->n_9728000_6[color="blue"];n_9728000_6[label="6: Inode(IIJXKYQFPEGVO):index.js -> Inode(QJSNITID4KDNO)"];n_9728000_6->n_9728000_7[color="blue"];n_9728000_7[label="7: Inode(JWFQTQXTTBAM4): -> Inode(JWFQTQXTTBAM4)"];n_9728000_7->n_9728000_8[color="blue"];n_9728000_8[label="8: Inode(JWFQTQXTTBAM4):album.svelte -> Inode(VRA6U3PZUW5WQ)"];n_9728000_8->n_9728000_9[color="blue"];n_9728000_9[label="9: Inode(JWFQTQXTTBAM4):album_track.svelte -> Inode(QLHMQ5W5CGJIO)"];n_9728000_9->n_9728000_10[color="blue"];n_9728000_10[label="10: Inode(JWFQTQXTTBAM4):playlist.svelte -> Inode(5CJTHSCSD76MA)"];n_9728000_10->n_9728000_11[color="blue"];n_9728000_11[label="11: Inode(JWFQTQXTTBAM4):track.svelte -> Inode(H57OXKYNDTI44)"];n_9728000_11->n_9728000_12[color="blue"];n_9728000_12[label="12: Inode(J6JGAUVUWUSBO): -> Inode(J6JGAUVUWUSBO)"];n_9728000_12->n_9728000_13[color="blue"];n_9728000_13[label="13: Inode(J6JGAUVUWUSBO):client.svelte -> Inode(QW7QHVJARSXUU)"];n_9728000_13->n_9728000_14[color="blue"];n_9728000_14[label="14: Inode(KILB4TFGRUWYO): -> Inode(KILB4TFGRUWYO)"];n_9728000_14->n_9728000_15[color="blue"];n_9728000_15[label="15: Inode(KILB4TFGRUWYO):user.js -> Inode(5IUPI52DKPDBS)"];n_9728000_15->n_9728000_16[color="blue"];n_9728000_16[label="16: Inode(LFVZUHPU5CAKS): -> Inode(LFVZUHPU5CAKS)"];n_9728000_16->n_9728000_17[color="blue"];n_9728000_17[label="17: Inode(LFVZUHPU5CAKS):pin.svelte -> Inode(SG2UN5I7QLN7A)"];n_9728000_17->n_9728000_18[color="blue"];n_9728000_18[label="18: Inode(LVAQPHMUKJPW2): -> Inode(LVAQPHMUKJPW2)"];n_9728000_18->n_9728000_19[color="blue"];n_9728000_19[label="19: Inode(LVAQPHMUKJPW2):_pin.svelte -> Inode(5SMMKDRXWOKGG)"];n_9728000_19->n_9728000_20[color="blue"];n_9728000_20[label="20: Inode(LVAQPHMUKJPW2):admin.svelte -> Inode(TINDCAXEPW6XC)"];n_9728000_20->n_9728000_21[color="blue"];n_9728000_21[label="21: Inode(LVAQPHMUKJPW2):client.svelte -> Inode(IIIJPMO6KEAAW)"];n_9728000_21->n_9728000_22[color="blue"];n_9728000_22[label="22: Inode(LXA4G77TCZMIA): -> Inode(LXA4G77TCZMIA)"];n_9728000_22->n_9728000_23[color="blue"];n_9728000_23[label="23: Inode(LXA4G77TCZMIA):_account -> Inode(3F4CWNT24Y26K)"];n_9728000_23->n_9728000_24[color="blue"];n_9728000_24[label="24: Inode(LXA4G77TCZMIA):_event_select -> Inode(TKXEXNWSWJ7KU)"];n_9728000_24->n_9728000_25[color="blue"];n_9728000_25[label="25: Inode(LXA4G77TCZMIA):_event_select.svelte -> Inode(263KQXTUNMAE6)"];n_9728000_25->n_9728000_26[color="blue"];n_9728000_26[label="26: Inode(LXA4G77TCZMIA):_log_in.svelte -> Inode(ZRNEIHKDP6WYM)"];n_9728000_26->n_9728000_27[color="blue"];n_9728000_27[label="27: Inode(LXA4G77TCZMIA):_sign_up.svelte -> Inode(I5V3M6EGCACT2)"];n_9728000_27->n_9728000_28[color="blue"];n_9728000_28[label="28: Inode(LXA4G77TCZMIA):account.svelte -> Inode(UDRU4XIBL6S3Q)"];n_9728000_28->n_9728000_29[color="blue"];n_9728000_29[label="29: Inode(LXA4G77TCZMIA):explore -> Inode(XS7ILRT4DODSM)"];n_9728000_29->n_9728000_30[color="blue"];n_9728000_30[label="30: Inode(LXA4G77TCZMIA):index.svelte -> Inode(YBO7PSIJXEWMW)"];n_9728000_30->n_9728000_31[color="blue"];n_9728000_31[label="31: Inode(LXA4G77TCZMIA):info.svelte -> Inode(SA42HG3UET25C)"];n_9728000_31->n_9728000_32[color="blue"];n_9728000_32[label="32: Inode(LXA4G77TCZMIA):tallies -> Inode(4QGMJHUA2VKVY)"];n_9728000_32->n_9728000_33[color="blue"];n_9728000_33[label="33: Inode(L4F3IGWMZ2T7O): -> Inode(L4F3IGWMZ2T7O)"];n_9728000_33->n_9728000_34[color="blue"];n_9728000_34[label="34: Inode(L4F3IGWMZ2T7O):area.yaml -> Inode(6LYALMMF5QBVW)"];n_9728000_34->n_9728000_35[color="blue"];n_9728000_35[label="35: Inode(L4F3IGWMZ2T7O):component.yaml -> Inode(C5M45PW73QPK2)"];n_9728000_35->n_9728000_36[color="blue"];n_9728000_36[label="36: Inode(L4F3IGWMZ2T7O):room.yaml -> Inode(2SCBQIIGC5VNY)"];n_9728000_36->n_9728000_37[color="blue"];n_9728000_37[label="37: Inode(L4F3IGWMZ2T7O):schema.yaml -> Inode(OIVBAWVB7O7QK)"];n_9728000_37->n_9728000_38[color="blue"];n_9728000_38[label="38: Inode(L4F3IGWMZ2T7O):schema_tree.yaml -> Inode(7MVP25HSK2FOS)"];n_9728000_38->n_9728000_39[color="blue"];n_9728000_39[label="39: Inode(L4F3IGWMZ2T7O):system.yaml -> Inode(3UFVMJGXBEKC2)"];n_9728000_39->n_9728000_40[color="blue"];n_9728000_40[label="40: Inode(MWO6HOVXPRJHC): -> Inode(MWO6HOVXPRJHC)"];n_9728000_40->n_9728000_41[color="blue"];n_9728000_41[label="41: Inode(MWO6HOVXPRJHC):event_auth.js -> Inode(46ZG3OGE7LSNW)"];n_9728000_41->n_9728000_42[color="blue"];n_9728000_42[label="42: Inode(MWO6HOVXPRJHC):user_auth.js -> Inode(FADI6NLB3NNA4)"];n_9728000_42->n_9728000_43[color="blue"];n_9728000_43[label="43: Inode(N2ELXII4EF5PE): -> Inode(N2ELXII4EF5PE)"];n_9728000_43->n_9728000_44[color="blue"];n_9728000_44[label="44: Inode(N2ELXII4EF5PE):datastore -> Inode(UEQ6N3RUPIS72)"];n_9728000_44->n_9728000_45[color="blue"];n_9728000_45[label="45: Inode(N2ELXII4EF5PE):datastore.js -> Inode(R54CACJWN5RJQ)"];n_9728000_45->n_9728000_46[color="blue"];n_9728000_46[label="46: Inode(N2ELXII4EF5PE):datastore.yaml -> Inode(7AHKCC4GE5HIA)"];n_9728000_46->n_9728000_47[color="blue"];n_9728000_47[label="47: Inode(N2ELXII4EF5PE):fixtures -> Inode(L4F3IGWMZ2T7O)"];n_9728000_47->n_9728000_48[color="blue"];n_9728000_48[label="48: Inode(N2ELXII4EF5PE):helper.js -> Inode(UUEVN7POPWZRI)"];n_9728000_48->n_9728000_49[color="blue"];n_9728000_49[label="49: Inode(N2ELXII4EF5PE):pointer -> Inode(5USYOF7HSPTLS)"];n_9728000_49->n_9728000_50[color="blue"];n_9728000_50[label="50: Inode(N2ELXII4EF5PE):pointer.js -> Inode(3TYWLIY6W44C6)"];n_9728000_50->n_9728000_51[color="blue"];n_9728000_51[label="51: Inode(N2ELXII4EF5PE):schema.js -> Inode(PCXWS2WVHV656)"];n_9728000_51->n_9728000_52[color="blue"];n_9728000_52[label="52: Inode(N2ELXII4EF5PE):tests.yaml -> Inode(SRWSU45S4MXUQ)"];n_9728000_52->n_9728000_53[color="blue"];n_9728000_53[label="53: Inode(N2ELXII4EF5PE):topic_tree.js -> Inode(OW23H46BY5XGG)"];n_9728000_53->n_9728000_54[color="blue"];n_9728000_54[label="54: Inode(N2ELXII4EF5PE):v3-compatible -> Inode(WB6PIH4O4EPXQ)"];n_9728000_54->n_9728000_55[color="blue"];n_9728000_55[label="55: Inode(O6W4GJHUO2OQY): -> Inode(O6W4GJHUO2OQY)"];n_9728000_55->n_9728000_56[color="blue"];n_9728000_56[label="56: Inode(O6W4GJHUO2OQY):grid.svelte -> Inode(TRKUPEIKQLPYI)"];n_9728000_56->n_9728000_57[color="blue"];n_9728000_57[label="57: Inode(O6W4GJHUO2OQY):header -> Inode(Y74VQVJLZBCQK)"];n_9728000_57->n_9728000_58[color="blue"];n_9728000_58[label="58: Inode(O6W4GJHUO2OQY):index.svelte -> Inode(UZP2LKGF5KUWM)"];n_9728000_58->n_9728000_59[color="blue"];n_9728000_59[label="59: Inode(O6W4GJHUO2OQY):inspect -> Inode(JWFQTQXTTBAM4)"];n_9728000_59->n_9728000_60[color="blue"];n_9728000_60[label="60: Inode(O6W4GJHUO2OQY):inspect.svelte -> Inode(ZDMRTTEPFX2YW)"];n_9728000_60->n_9728000_61[color="blue"];n_9728000_61[label="61: Inode(O6W4GJHUO2OQY):inspect_router.svelte -> Inode(R5KAAVZJDOBTO)"];n_9728000_61->n_9728000_62[color="blue"];n_9728000_62[label="62: Inode(O6W4GJHUO2OQY):item.svelte -> Inode(REK2TCSSCZXNM)"];n_9728000_62->n_9728000_63[color="blue"];n_9728000_63[label="63: Inode(O6W4GJHUO2OQY):line -> Inode(CVXDRUYJN25PO)"];n_9728000_63->n_9728000_64[color="blue"];n_9728000_64[label="64: Inode(O6W4GJHUO2OQY):list.svelte -> Inode(NONQLJPKV7RLK)"];n_9728000_64->n_9728000_65[color="blue"];n_9728000_65[label="65: Inode(O6W4GJHUO2OQY):meta -> Inode(GN4XK57WEBIZ2)"];n_9728000_65->n_9728000_66[color="blue"];n_9728000_66[label="66: Inode(O6W4GJHUO2OQY):search_results.svelte -> Inode(HGSX5EUHWOQEE)"];n_9728000_66->n_9728000_67[color="blue"];n_9728000_67[label="67: Inode(PIUOP5YSQE4RC): -> Inode(PIUOP5YSQE4RC)"];n_9728000_67->n_9728000_68[color="blue"];n_9728000_68[label="68: Inode(PZD6LOZIK35ES): -> Inode(PZD6LOZIK35ES)"];n_9728000_68->n_9728000_69[color="blue"];n_9728000_69[label="69: Inode(QIVCPWRQJNZS2): -> Inode(QIVCPWRQJNZS2)"];n_9728000_69->n_9728000_70[color="blue"];n_9728000_70[label="70: Inode(QIVCPWRQJNZS2):album.svelte -> Inode(XPATCSY5XREGU)"];}subgraph cluster9756672 {label="Page 9756672, rc 0 3410";color=black;n_9756672_0[label="0: Inode(QIVCPWRQJNZS2):categories.svelte -> Inode(26I4RIFGWU76E)"];n_9756672_0->n_9756672_1[color="blue"];n_9756672_1[label="1: Inode(QIVCPWRQJNZS2):category.svelte -> Inode(6G3INXRFY7DNK)"];n_9756672_1->n_9756672_2[color="blue"];n_9756672_2[label="2: Inode(QIVCPWRQJNZS2):playlist.svelte -> Inode(CGAUFIV4SCO62)"];n_9756672_2->n_9756672_3[color="blue"];n_9756672_3[label="3: Inode(QIVCPWRQJNZS2):playlists.svelte -> Inode(R4RI26S4ZXTEQ)"];n_9756672_3->n_9756672_4[color="blue"];n_9756672_4[label="4: Inode(QIVCPWRQJNZS2):search_results.svelte -> Inode(ZPZ73HIMHBPJM)"];n_9756672_4->n_9756672_5[color="blue"];n_9756672_5[label="5: Inode(QIVCPWRQJNZS2):track.svelte -> Inode(ASLPYCAKNA43A)"];n_9756672_5->n_9756672_6[color="blue"];n_9756672_6[label="6: Inode(QIVCPWRQJNZS2):tracks.svelte -> Inode(7MV7KPBTB3QVQ)"];n_9756672_6->n_9756672_7[color="blue"];n_9756672_7[label="7: Inode(QWW5O47FCNWJC): -> Inode(QWW5O47FCNWJC)"];n_9756672_7->n_9756672_8[color="blue"];n_9756672_8[label="8: Inode(QZDUDJDCZJFQC): -> Inode(QZDUDJDCZJFQC)"];n_9756672_8->n_9756672_9[color="blue"];n_9756672_9[label="9: Inode(QZDUDJDCZJFQC):authorization.js -> Inode(S5QXOGFCOWY2M)"];n_9756672_9->n_9756672_10[color="blue"];n_9756672_10[label="10: Inode(QZDUDJDCZJFQC):send.js -> Inode(MQSRSSLXTSELC)"];n_9756672_10->n_9756672_11[color="blue"];n_9756672_11[label="11: Inode(QZDUDJDCZJFQC):spotify -> Inode(PIUOP5YSQE4RC)"];n_9756672_11->n_9756672_12[color="blue"];n_9756672_12[label="12: Inode(QZDUDJDCZJFQC):subscribe.js -> Inode(E2HPDKYVMEWZG)"];n_9756672_12->n_9756672_13[color="blue"];n_9756672_13[label="13: Inode(QZDUDJDCZJFQC):theme.js -> Inode(6RYHU3BG66OKE)"];n_9756672_13->n_9756672_14[color="blue"];n_9756672_14[label="14: Inode(QZDUDJDCZJFQC):unsubscribe.js -> Inode(G7XTEX2JUJDCG)"];n_9756672_14->n_9756672_15[color="blue"];n_9756672_15[label="15: Inode(QZDUDJDCZJFQC):websocket -> Inode(IIJXKYQFPEGVO)"];n_9756672_15->n_9756672_16[color="blue"];n_9756672_16[label="16: Inode(SPVUATTTWOMBC): -> Inode(SPVUATTTWOMBC)"];n_9756672_16->n_9756672_17[color="blue"];n_9756672_17[label="17: Inode(SPVUATTTWOMBC):__error.svelte -> Inode(V3TG7DZP2376A)"];n_9756672_17->n_9756672_18[color="blue"];n_9756672_18[label="18: Inode(SPVUATTTWOMBC):__layout.svelte -> Inode(AUT5SNQA2HDNK)"];n_9756672_18->n_9756672_19[color="blue"];n_9756672_19[label="19: Inode(SPVUATTTWOMBC):admin -> Inode(FPEFGAHOE333Y)"];n_9756672_19->n_9756672_20[color="blue"];n_9756672_20[label="20: Inode(SPVUATTTWOMBC):client -> Inode(LXA4G77TCZMIA)"];n_9756672_20->n_9756672_21[color="blue"];n_9756672_21[label="21: Inode(SPVUATTTWOMBC):index.svelte -> Inode(JDSTTHRVQEQO6)"];n_9756672_21->n_9756672_22[color="blue"];n_9756672_22[label="22: Inode(SPVUATTTWOMBC):oauth -> Inode(LVAQPHMUKJPW2)"];n_9756672_22->n_9756672_23[color="blue"];n_9756672_23[label="23: Inode(S6K7IPAUN7LBY): -> Inode(S6K7IPAUN7LBY)"];n_9756672_23->n_9756672_24[color="blue"];n_9756672_24[label="24: Inode(S6K7IPAUN7LBY):boolean.svelte -> Inode(RPPTHPVGYWZ6C)"];n_9756672_24->n_9756672_25[color="blue"];n_9756672_25[label="25: Inode(S6K7IPAUN7LBY):filter.svelte -> Inode(FI3MAYGRVNIAU)"];n_9756672_25->n_9756672_26[color="blue"];n_9756672_26[label="26: Inode(S6K7IPAUN7LBY):select.svelte -> Inode(HDIGDKSZZP2Q2)"];n_9756672_26->n_9756672_27[color="blue"];n_9756672_27[label="27: Inode(S6K7IPAUN7LBY):string.svelte -> Inode(FGYYVSXN4TNNY)"];n_9756672_27->n_9756672_28[color="blue"];n_9756672_28[label="28: Inode(TACHSLXLDQETM): -> Inode(TACHSLXLDQETM)"];n_9756672_28->n_9756672_29[color="blue"];n_9756672_29[label="29: Inode(TACHSLXLDQETM):.gitignore -> Inode(ZLZM2ASTO3GTA)"];n_9756672_29->n_9756672_30[color="blue"];n_9756672_30[label="30: Inode(TACHSLXLDQETM):README.md -> Inode(ZVAHLLO3PNRZY)"];n_9756672_30->n_9756672_31[color="blue"];n_9756672_31[label="31: Inode(TACHSLXLDQETM):benchmark -> Inode(C3ZNORJPW55RC)"];n_9756672_31->n_9756672_32[color="blue"];n_9756672_32[label="32: Inode(TACHSLXLDQETM):package.json -> Inode(YM4XJWTH2XKVM)"];n_9756672_32->n_9756672_33[color="blue"];n_9756672_33[label="33: Inode(TACHSLXLDQETM):src -> Inode(DD3J6NXBHNLGU)"];n_9756672_33->n_9756672_34[color="blue"];n_9756672_34[label="34: Inode(TACHSLXLDQETM):test -> Inode(N2ELXII4EF5PE)"];n_9756672_34->n_9756672_35[color="blue"];n_9756672_35[label="35: Inode(TKXEXNWSWJ7KU): -> Inode(TKXEXNWSWJ7KU)"];n_9756672_35->n_9756672_36[color="blue"];n_9756672_36[label="36: Inode(TKXEXNWSWJ7KU):name.svelte -> Inode(RYIOCDX2ZP7O2)"];n_9756672_36->n_9756672_37[color="blue"];n_9756672_37[label="37: Inode(UEQ6N3RUPIS72): -> Inode(UEQ6N3RUPIS72)"];n_9756672_37->n_9756672_38[color="blue"];n_9756672_38[label="38: Inode(UEQ6N3RUPIS72):base.js -> Inode(AUFOXIVZYTB7K)"];n_9756672_38->n_9756672_39[color="blue"];n_9756672_39[label="39: Inode(UEQ6N3RUPIS72):chain.js -> Inode(NCV35UUVAJCM2)"];n_9756672_39->n_9756672_40[color="blue"];n_9756672_40[label="40: Inode(UEQ6N3RUPIS72):convenience.js -> Inode(ZE3ZKVKOYFSGS)"];n_9756672_40->n_9756672_41[color="blue"];n_9756672_41[label="41: Inode(UEQ6N3RUPIS72):external.js -> Inode(MAQN3T44U7KQI)"];n_9756672_41->n_9756672_42[color="blue"];n_9756672_42[label="42: Inode(UEQ6N3RUPIS72):hooks.js -> Inode(W6LCU4BX6FDKC)"];n_9756672_42->n_9756672_43[color="blue"];n_9756672_43[label="43: Inode(UEQ6N3RUPIS72):pubsub.js -> Inode(OKSU5WIOAMZRG)"];n_9756672_43->n_9756672_44[color="blue"];n_9756672_44[label="44: Inode(UEQ6N3RUPIS72):schema.js -> Inode(DAW22JP7F2KSA)"];n_9756672_44->n_9756672_45[color="blue"];n_9756672_45[label="45: Inode(UEQ6N3RUPIS72):transact.js -> Inode(Z2OUZ4VQAWP64)"];n_9756672_45->n_9756672_46[color="blue"];n_9756672_46[label="46: Inode(UFNCC47RMZ2R6): -> Inode(UFNCC47RMZ2R6)"];n_9756672_46->n_9756672_47[color="blue"];n_9756672_47[label="47: Inode(UFNCC47RMZ2R6):Icon.svelte -> Inode(4UMIMRFQB4JKY)"];n_9756672_47->n_9756672_48[color="blue"];n_9756672_48[label="48: Inode(UFNCC47RMZ2R6):background -> Inode(AYO4INBMW4MC6)"];n_9756672_48->n_9756672_49[color="blue"];n_9756672_49[label="49: Inode(UFNCC47RMZ2R6):global.svelte -> Inode(BX65WPXO3SBDK)"];n_9756672_49->n_9756672_50[color="blue"];n_9756672_50[label="50: Inode(UFNCC47RMZ2R6):name.svelte -> Inode(TNNGQOLKFRBSE)"];n_9756672_50->n_9756672_51[color="blue"];n_9756672_51[label="51: Inode(UFNCC47RMZ2R6):navigation -> Inode(J6JGAUVUWUSBO)"];n_9756672_51->n_9756672_52[color="blue"];n_9756672_52[label="52: Inode(UFNCC47RMZ2R6):prompts -> Inode(ZHWMZ7OLLI2DC)"];n_9756672_52->n_9756672_53[color="blue"];n_9756672_53[label="53: Inode(UFNCC47RMZ2R6):slots -> Inode(S6K7IPAUN7LBY)"];n_9756672_53->n_9756672_54[color="blue"];n_9756672_54[label="54: Inode(U5SVCHKJ2EPXQ): -> Inode(U5SVCHKJ2EPXQ)"];n_9756672_54->n_9756672_55[color="blue"];n_9756672_55[label="55: Inode(U5SVCHKJ2EPXQ):base.js -> Inode(RHGR5HAAWQUHA)"];n_9756672_55->n_9756672_56[color="blue"];n_9756672_56[label="56: Inode(U5SVCHKJ2EPXQ):convenience.js -> Inode(M772QGPRSESBA)"];n_9756672_56->n_9756672_57[color="blue"];n_9756672_57[label="57: Inode(U5SVCHKJ2EPXQ):external.js -> Inode(ZFL463SNDJCWK)"];n_9756672_57->n_9756672_58[color="blue"];n_9756672_58[label="58: Inode(U5SVCHKJ2EPXQ):pubsub.js -> Inode(M36WQCXBJTLKC)"];n_9756672_58->n_9756672_59[color="blue"];n_9756672_59[label="59: Inode(VDXMC7T3ZYEYG): -> Inode(VDXMC7T3ZYEYG)"];n_9756672_59->n_9756672_60[color="blue"];n_9756672_60[label="60: Inode(VDXMC7T3ZYEYG):coppice.js -> Inode(PLYE6FNYWKQY4)"];n_9756672_60->n_9756672_61[color="blue"];n_9756672_61[label="61: Inode(VDXMC7T3ZYEYG):is_coppice.js -> Inode(PTCUKEWM4Q6YG)"];n_9756672_61->n_9756672_62[color="blue"];n_9756672_62[label="62: Inode(VDXMC7T3ZYEYG):is_empty.js -> Inode(AIEDUCHX6VS6W)"];n_9756672_62->n_9756672_63[color="blue"];n_9756672_63[label="63: Inode(VDXMC7T3ZYEYG):is_traversable.js -> Inode(RQI2R5EYSAQ4K)"];n_9756672_63->n_9756672_64[color="blue"];n_9756672_64[label="64: Inode(VSXBCX4TPIMJ4): -> Inode(VSXBCX4TPIMJ4)"];n_9756672_64->n_9756672_65[color="blue"];n_9756672_65[label="65: Inode(VSXBCX4TPIMJ4):global.css -> Inode(WUOCEGDPKW3R2)"];n_9756672_65->n_9756672_66[color="blue"];n_9756672_66[label="66: Inode(WB6PIH4O4EPXQ): -> Inode(WB6PIH4O4EPXQ)"];n_9756672_66->n_9756672_67[color="blue"];n_9756672_67[label="67: Inode(WB6PIH4O4EPXQ):datastore -> Inode(U5SVCHKJ2EPXQ)"];n_9756672_67->n_9756672_68[color="blue"];n_9756672_68[label="68: Inode(WB6PIH4O4EPXQ):datastore.js -> Inode(YMTY2KW35DQAU)"];n_9756672_68->n_9756672_69[color="blue"];n_9756672_69[label="69: Inode(WB6PIH4O4EPXQ):datastore.yaml -> Inode(P34TGHAAECU2W)"];n_9756672_69->n_9756672_70[color="blue"];n_9756672_70[label="70: Inode(WB6PIH4O4EPXQ):fixtures -> Inode(6NSCSDVPRVUD6)"];n_9756672_70->n_9756672_71[color="blue"];n_9756672_71[label="71: Inode(WB6PIH4O4EPXQ):helper.js -> Inode(ZH3E3UOS5CADE)"];n_9756672_71->n_9756672_72[color="blue"];n_9756672_72[label="72: Inode(WB6PIH4O4EPXQ):patterns -> Inode(EWGIKU7N4TFWO)"];n_9756672_72->n_9756672_73[color="blue"];n_9756672_73[label="73: Inode(WB6PIH4O4EPXQ):pointer.yaml -> Inode(7D2JIKU4LZB46)"];n_9756672_73->n_9756672_74[color="blue"];n_9756672_74[label="74: Inode(WB6PIH4O4EPXQ):schema.js -> Inode(V5ARTKEXQAG64)"];n_9756672_74->n_9756672_75[color="blue"];n_9756672_75[label="75: Inode(WB6PIH4O4EPXQ):tests.yaml -> Inode(RNLJ5LJGUSEFU)"];n_9756672_75->n_9756672_76[color="blue"];n_9756672_76[label="76: Inode(WB6PIH4O4EPXQ):topic.yaml -> Inode(MRXIDRFCR6BMI)"];n_9756672_76->n_9756672_77[color="blue"];n_9756672_77[label="77: Inode(WB6PIH4O4EPXQ):topic_tree.dep -> Inode(WIWUDZKNML6KS)"];n_9756672_77->n_9756672_78[color="blue"];n_9756672_78[label="78: Inode(WQMKGUAN3HGPM): -> Inode(WQMKGUAN3HGPM)"];n_9756672_78->n_9756672_79[color="blue"];n_9756672_79[label="79: Inode(WYD4M6RXKB7OC): -> Inode(WYD4M6RXKB7OC)"];n_9756672_79->n_9756672_80[color="blue"];n_9756672_80[label="80: Inode(WYD4M6RXKB7OC):_deploy-remote.sh -> Inode(3TL33Z6QL5BO4)"];n_9756672_80->n_9756672_81[color="blue"];n_9756672_81[label="81: Inode(WYD4M6RXKB7OC):_deploy.sh -> Inode(KQTF3WPQXAVV6)"];n_9756672_81->n_9756672_82[color="blue"];n_9756672_82[label="82: Inode(WYD4M6RXKB7OC):_test.sh -> Inode(ZTMX7LIPRAHI2)"];n_9756672_82->n_9756672_83[color="blue"];n_9756672_83[label="83: Inode(WYD4M6RXKB7OC):build.sh -> Inode(T4KYTWLLF22P4)"];n_9756672_83->n_9756672_84[color="blue"];n_9756672_84[label="84: Inode(WYD4M6RXKB7OC):rsync-deploy.ignore -> Inode(GDCBHLWPWTI6U)"];n_9756672_84->n_9756672_85[color="blue"];n_9756672_85[label="85: Inode(WYD4M6RXKB7OC):start.sh -> Inode(3CTO5MJNEJ7G6)"];n_9756672_85->n_9756672_86[color="blue"];n_9756672_86[label="86: Inode(XS7ILRT4DODSM): -> Inode(XS7ILRT4DODSM)"];n_9756672_86->n_9756672_87[color="blue"];n_9756672_87[label="87: Inode(XS7ILRT4DODSM):_categories -> Inode(QIVCPWRQJNZS2)"];n_9756672_87->n_9756672_88[color="blue"];n_9756672_88[label="88: Inode(XS7ILRT4DODSM):_explore -> Inode(O6W4GJHUO2OQY)"];n_9756672_88->n_9756672_89[color="blue"];n_9756672_89[label="89: Inode(XS7ILRT4DODSM):categories.svelte -> Inode(TNUOEZIDMWNAS)"];n_9756672_89->n_9756672_90[color="blue"];n_9756672_90[label="90: Inode(XS7ILRT4DODSM):index.svelte -> Inode(UFCB7DT5I536C)"];n_9756672_90->n_9756672_91[color="blue"];n_9756672_91[label="91: Inode(XS7ILRT4DODSM):navigate.js -> Inode(EZRQRQB2W3T2M)"];n_9756672_91->n_9756672_92[color="blue"];n_9756672_92[label="92: Inode(XS7ILRT4DODSM):search.svelte -> Inode(YEBOLZGYVGG6E)"];n_9756672_92->n_9756672_93[color="blue"];n_9756672_93[label="93: Inode(X7MBB5IZL2DBA): -> Inode(X7MBB5IZL2DBA)"];n_9756672_93->n_9756672_94[color="blue"];n_9756672_94[label="94: Inode(X7MBB5IZL2DBA):_test.sh -> Inode(CKKEQRIACBCQ4)"];n_9756672_94->n_9756672_95[color="blue"];n_9756672_95[label="95: Inode(X7MBB5IZL2DBA):build.sh -> Inode(5DZXVOWKBSMGI)"];n_9756672_95->n_9756672_96[color="blue"];n_9756672_96[label="96: Inode(X7MBB5IZL2DBA):rsync-deploy.ignore -> Inode(HRPR7SKXCJN44)"];n_9756672_96->n_9756672_97[color="blue"];n_9756672_97[label="97: Inode(X7MBB5IZL2DBA):start.sh -> Inode(IAY4KMPNWY2UC)"];n_9756672_97->n_9756672_98[color="blue"];n_9756672_98[label="98: Inode(Y74VQVJLZBCQK): -> Inode(Y74VQVJLZBCQK)"];n_9756672_98->n_9756672_99[color="blue"];n_9756672_99[label="99: Inode(Y74VQVJLZBCQK):album.svelte -> Inode(MEBSSOZEGZBUS)"];n_9756672_99->n_9756672_100[color="blue"];n_9756672_100[label="100: Inode(Y74VQVJLZBCQK):artist.svelte -> Inode(SCXNCVLQ53QUS)"];n_9756672_100->n_9756672_101[color="blue"];n_9756672_101[label="101: Inode(Y74VQVJLZBCQK):playlist.svelte -> Inode(A5F4M52V6BAA6)"];n_9756672_101->n_9756672_102[color="blue"];n_9756672_102[label="102: Inode(Y74VQVJLZBCQK):track.svelte -> Inode(HZL4LIPH35X46)"];n_9756672_102->n_9756672_103[color="blue"];n_9756672_103[label="103: Inode(ZHWMZ7OLLI2DC): -> Inode(ZHWMZ7OLLI2DC)"];n_9756672_103->n_9756672_104[color="blue"];n_9756672_104[label="104: Inode(ZHWMZ7OLLI2DC):logout.svelte -> Inode(TR4NT2N6B7WVA)"];}subgraph cluster9805824 {label="Page 9805824, rc 0 2718";color=black;n_9805824_0[label="0: Inode(3DJEOGW5L5HCE):commands.js -> Inode(HLTW65F3OX2I6)"];n_9805824_0->n_9805824_1[color="blue"];n_9805824_1[label="1: Inode(3DJEOGW5L5HCE):index.js -> Inode(NM333TUJAJ2CA)"];n_9805824_1->n_9805824_2[color="blue"];n_9805824_2[label="2: Inode(3F4CWNT24Y26K): -> Inode(3F4CWNT24Y26K)"];n_9805824_2->n_9805824_3[color="blue"];n_9805824_3[label="3: Inode(3F4CWNT24Y26K):basic -> Inode(3MMLSKVUCENQE)"];n_9805824_3->n_9805824_4[color="blue"];n_9805824_4[label="4: Inode(3F4CWNT24Y26K):business -> Inode(4ORPES5WVHU6Y)"];n_9805824_4->n_9805824_5[color="blue"];n_9805824_5[label="5: Inode(3F4CWNT24Y26K):event.svelte -> Inode(2MV5VU5G3OAFU)"];n_9805824_5->n_9805824_6[color="blue"];n_9805824_6[label="6: Inode(3F4CWNT24Y26K):find.svelte -> Inode(NGPD2KP5CNANY)"];n_9805824_6->n_9805824_7[color="blue"];n_9805824_7[label="7: Inode(3F4CWNT24Y26K):free -> Inode(QWW5O47FCNWJC)"];n_9805824_7->n_9805824_8[color="blue"];n_9805824_8[label="8: Inode(3F4CWNT24Y26K):manage -> Inode(FACHIVHPFYCQA)"];n_9805824_8->n_9805824_9[color="blue"];n_9805824_9[label="9: Inode(3F4CWNT24Y26K):manage.svelte -> Inode(2WDUNOOWYEEAI)"];n_9805824_9->n_9805824_10[color="blue"];n_9805824_10[label="10: Inode(3F4CWNT24Y26K):user_events.svelte -> Inode(D5GT6MAEWVN3E)"];n_9805824_10->n_9805824_11[color="blue"];n_9805824_11[label="11: Inode(3MMLSKVUCENQE): -> Inode(3MMLSKVUCENQE)"];n_9805824_11->n_9805824_12[color="blue"];n_9805824_12[label="12: Inode(4BEYCSKD6NO7G): -> Inode(4BEYCSKD6NO7G)"];n_9805824_12->n_9805824_13[color="blue"];n_9805824_13[label="13: Inode(4BEYCSKD6NO7G):spotify -> Inode(MWO6HOVXPRJHC)"];n_9805824_13->n_9805824_14[color="blue"];n_9805824_14[label="14: Inode(4BEYCSKD6NO7G):spotify.js -> Inode(GRXYBAHYY3CNC)"];n_9805824_14->n_9805824_15[color="blue"];n_9805824_15[label="15: Inode(4ORPES5WVHU6Y): -> Inode(4ORPES5WVHU6Y)"];n_9805824_15->n_9805824_16[color="blue"];n_9805824_16[label="16: Inode(4QGMJHUA2VKVY): -> Inode(4QGMJHUA2VKVY)"];n_9805824_16->n_9805824_17[color="blue"];n_9805824_17[label="17: Inode(4QGMJHUA2VKVY):_event.svelte -> Inode(LIG2ID3QLCZXE)"];n_9805824_17->n_9805824_18[color="blue"];n_9805824_18[label="18: Inode(4QGMJHUA2VKVY):_item.svelte -> Inode(EKGMRO63ZPTAQ)"];n_9805824_18->n_9805824_19[color="blue"];n_9805824_19[label="19: Inode(4QGMJHUA2VKVY):index.svelte -> Inode(X6DYH2NU5SYEG)"];n_9805824_19->n_9805824_20[color="blue"];n_9805824_20[label="20: Inode(5ODKHB7CLODEU): -> Inode(5ODKHB7CLODEU)"];n_9805824_20->n_9805824_21[color="blue"];n_9805824_21[label="21: Inode(5ODKHB7CLODEU):Icon.svelte -> Inode(UYPXQBB5W67YW)"];n_9805824_21->n_9805824_22[color="blue"];n_9805824_22[label="22: Inode(5USYOF7HSPTLS): -> Inode(5USYOF7HSPTLS)"];n_9805824_22->n_9805824_23[color="blue"];n_9805824_23[label="23: Inode(5USYOF7HSPTLS):base.js -> Inode(O5VDB4HZVW7T2)"];n_9805824_23->n_9805824_24[color="blue"];n_9805824_24[label="24: Inode(5USYOF7HSPTLS):controlenvy.js -> Inode(NY4T5WSV4EJ72)"];n_9805824_24->n_9805824_25[color="blue"];n_9805824_25[label="25: Inode(6NSCSDVPRVUD6): -> Inode(6NSCSDVPRVUD6)"];n_9805824_25->n_9805824_26[color="blue"];n_9805824_26[label="26: Inode(6NSCSDVPRVUD6):area.yaml -> Inode(Y3B3GCUUHVFSW)"];n_9805824_26->n_9805824_27[color="blue"];n_9805824_27[label="27: Inode(6NSCSDVPRVUD6):component.yaml -> Inode(G3ZNRKPQQTNEI)"];n_9805824_27->n_9805824_28[color="blue"];n_9805824_28[label="28: Inode(6NSCSDVPRVUD6):room.yaml -> Inode(53UVDWGEJLQ5M)"];n_9805824_28->n_9805824_29[color="blue"];n_9805824_29[label="29: Inode(6NSCSDVPRVUD6):schema.yaml -> Inode(F6G2WMFXHEPC4)"];n_9805824_29->n_9805824_30[color="blue"];n_9805824_30[label="30: Inode(6NSCSDVPRVUD6):schema_tree.yaml -> Inode(NMHG2L57ESID2)"];n_9805824_30->n_9805824_31[color="blue"];n_9805824_31[label="31: Inode(6NSCSDVPRVUD6):system.yaml -> Inode(3LPVG62PMSPBQ)"];n_9805824_31->n_9805824_32[color="blue"];n_9805824_32[label="32: Inode(6TMTDO45E3YQY): -> Inode(6TMTDO45E3YQY)"];n_9805824_32->n_9805824_33[color="blue"];n_9805824_33[label="33: Inode(6TMTDO45E3YQY):api.js -> Inode(FUTFDFPW44524)"];n_9805824_33->n_9805824_34[color="blue"];n_9805824_34[label="34: Inode(6TMTDO45E3YQY):base.js -> Inode(Y3UJQKQS4MP2K)"];n_9805824_34->n_9805824_35[color="blue"];n_9805824_35[label="35: Inode(6TMTDO45E3YQY):convenience.js -> Inode(AUMQ4GZOGACUO)"];n_9805824_35->n_9805824_36[color="blue"];n_9805824_36[label="36: Inode(6TMTDO45E3YQY):event.js -> Inode(FBA35XTQ2ZG3G)"];n_9805824_36->n_9805824_37[color="blue"];n_9805824_37[label="37: Inode(6TMTDO45E3YQY):helpers -> Inode(VDXMC7T3ZYEYG)"];n_9805824_37->n_9805824_38[color="blue"];n_9805824_38[label="38: Inode(6TMTDO45E3YQY):parser.js -> Inode(2USC4IYBTLXAQ)"];n_9805824_38->n_9805824_39[color="blue"];n_9805824_39[label="39: Inode(6TMTDO45E3YQY):pointer.js -> Inode(6LIZKWXUAC2XI)"];n_9805824_39->n_9805824_40[color="blue"];n_9805824_40[label="40: Inode(6TMTDO45E3YQY):svelte.js -> Inode(VKY4R6MG2WJRM)"];n_9805824_40->n_9805824_41[color="blue"];n_9805824_41[label="41: Inode(6TMTDO45E3YQY):topic.js -> Inode(7JIIEKIZZVY32)"];n_9805824_41->n_9805824_42[color="blue"];n_9805824_42[label="42: Inode(6TMTDO45E3YQY):tree.js -> Inode(5NGRCXLCVIDBS)"];n_9805824_42->n_9805824_43[color="blue"];n_9805824_43[label="43: Inode(6XVIYWEUJNQTS): -> Inode(6XVIYWEUJNQTS)"];n_9805824_43->n_9805824_44[color="blue"];n_9805824_44[label="44: Inode(6XVIYWEUJNQTS):lint.sh -> Inode(XKNWE7VUUVIM6)"];n_9805824_44->n_9805824_45[color="blue"];n_9805824_45[label="45: Inode(6X3XBPT7AZ35O): -> Inode(6X3XBPT7AZ35O)"];n_9805824_45->n_9805824_46[color="blue"];n_9805824_46[label="46: Inode(6X3XBPT7AZ35O):spec.js -> Inode(5IJ7O2D3AHJ6Q)"];n_9805824_46->n_9805824_47[color="blue"];n_9805824_47[label="47: Inode(65TLXQ7KZYYJ4): -> Inode(65TLXQ7KZYYJ4)"];n_9805824_47->n_9805824_48[color="blue"];n_9805824_48[label="48: Inode(65TLXQ7KZYYJ4):.autoenv.zsh -> Inode(6NPIRHGIBQ6ME)"];n_9805824_48->n_9805824_49[color="blue"];n_9805824_49[label="49: Inode(65TLXQ7KZYYJ4):.bin -> Inode(6XVIYWEUJNQTS)"];n_9805824_49->n_9805824_50[color="blue"];n_9805824_50[label="50: Inode(65TLXQ7KZYYJ4):admin -> Inode(GHPSHGSWVRTC2)"];n_9805824_50->n_9805824_51[color="blue"];n_9805824_51[label="51: Inode(65TLXQ7KZYYJ4):djiny -> Inode(73KSRBC4ZOXC2)"];n_9805824_51->n_9805824_52[color="blue"];n_9805824_52[label="52: Inode(65TLXQ7KZYYJ4):package-lock.json -> Inode(XXOOLDCHDBDHG)"];n_9805824_52->n_9805824_53[color="blue"];n_9805824_53[label="53: Inode(65TLXQ7KZYYJ4):pnpm-lock.yaml -> Inode(W3Q6QN5724GXE)"];n_9805824_53->n_9805824_54[color="blue"];n_9805824_54[label="54: Inode(65TLXQ7KZYYJ4):pnpm-workspace.yaml -> Inode(GM72ONXBVS64G)"];n_9805824_54->n_9805824_55[color="blue"];n_9805824_55[label="55: Inode(7BD75A5OV3RRU): -> Inode(7BD75A5OV3RRU)"];n_9805824_55->n_9805824_56[color="blue"];n_9805824_56[label="56: Inode(7BD75A5OV3RRU):fixtures -> Inode(FNO44B3I6OJIG)"];n_9805824_56->n_9805824_57[color="blue"];n_9805824_57[label="57: Inode(7BD75A5OV3RRU):integration -> Inode(6X3XBPT7AZ35O)"];n_9805824_57->n_9805824_58[color="blue"];n_9805824_58[label="58: Inode(7BD75A5OV3RRU):plugins -> Inode(ESKHFJ2QX7P5C)"];n_9805824_58->n_9805824_59[color="blue"];n_9805824_59[label="59: Inode(7BD75A5OV3RRU):support -> Inode(3DJEOGW5L5HCE)"];n_9805824_59->n_9805824_60[color="blue"];n_9805824_60[label="60: Inode(7GVTKXM3HLWNQ): -> Inode(7GVTKXM3HLWNQ)"];n_9805824_60->n_9805824_61[color="blue"];n_9805824_61[label="61: Inode(7GVTKXM3HLWNQ):api.js -> Inode(5Q4L6H5VPXEKO)"];n_9805824_61->n_9805824_62[color="blue"];n_9805824_62[label="62: Inode(7GVTKXM3HLWNQ):authorization.js -> Inode(3V5UJCRQVV52C)"];n_9805824_62->n_9805824_63[color="blue"];n_9805824_63[label="63: Inode(7GVTKXM3HLWNQ):components -> Inode(UFNCC47RMZ2R6)"];n_9805824_63->n_9805824_64[color="blue"];n_9805824_64[label="64: Inode(7GVTKXM3HLWNQ):drivers -> Inode(4BEYCSKD6NO7G)"];n_9805824_64->n_9805824_65[color="blue"];n_9805824_65[label="65: Inode(7GVTKXM3HLWNQ):models -> Inode(KILB4TFGRUWYO)"];n_9805824_65->n_9805824_66[color="blue"];n_9805824_66[label="66: Inode(7GVTKXM3HLWNQ):send.js -> Inode(ZEKLTYQNM5DBO)"];n_9805824_66->n_9805824_67[color="blue"];n_9805824_67[label="67: Inode(7GVTKXM3HLWNQ):subscribe.js -> Inode(FRR3RYWBJ66RO)"];n_9805824_67->n_9805824_68[color="blue"];n_9805824_68[label="68: Inode(7GVTKXM3HLWNQ):theme.js -> Inode(37C3XDBYLHIOS)"];n_9805824_68->n_9805824_69[color="blue"];n_9805824_69[label="69: Inode(7GVTKXM3HLWNQ):unsubscribe.js -> Inode(42T3SEQPCQ2NQ)"];n_9805824_69->n_9805824_70[color="blue"];n_9805824_70[label="70: Inode(7GVTKXM3HLWNQ):websocket -> Inode(FWTL334DDZDFG)"];n_9805824_70->n_9805824_71[color="blue"];n_9805824_71[label="71: Inode(73KSRBC4ZOXC2): -> Inode(73KSRBC4ZOXC2)"];n_9805824_71->n_9805824_72[color="blue"];n_9805824_72[label="72: Inode(73KSRBC4ZOXC2):.autoenv.zsh -> Inode(QOWPYVSW7ZQE6)"];n_9805824_72->n_9805824_73[color="blue"];n_9805824_73[label="73: Inode(73KSRBC4ZOXC2):.bin -> Inode(X7MBB5IZL2DBA)"];n_9805824_73->n_9805824_74[color="blue"];n_9805824_74[label="74: Inode(73KSRBC4ZOXC2):.eslintrc.cjs -> Inode(V5WFDITISXTCE)"];n_9805824_74->n_9805824_75[color="blue"];n_9805824_75[label="75: Inode(73KSRBC4ZOXC2):.gitignore -> Inode(RETQ6VPCV3KZW)"];n_9805824_75->n_9805824_76[color="blue"];n_9805824_76[label="76: Inode(73KSRBC4ZOXC2):.npmrc -> Inode(RYEFEZ45PN4MA)"];n_9805824_76->n_9805824_77[color="blue"];n_9805824_77[label="77: Inode(73KSRBC4ZOXC2):.prettierignore -> Inode(XVUKOURL7SMHU)"];n_9805824_77->n_9805824_78[color="blue"];n_9805824_78[label="78: Inode(73KSRBC4ZOXC2):.prettierrc -> Inode(33H5CQLJPJTXG)"];n_9805824_78->n_9805824_79[color="blue"];n_9805824_79[label="79: Inode(73KSRBC4ZOXC2):README.md -> Inode(PA6SDKU4QOTA2)"];n_9805824_79->n_9805824_80[color="blue"];n_9805824_80[label="80: Inode(73KSRBC4ZOXC2):package-lock.json -> Inode(6HBI4WGQ3OVCW)"];n_9805824_80->n_9805824_81[color="blue"];n_9805824_81[label="81: Inode(73KSRBC4ZOXC2):package.json -> Inode(FW7A5FRRVA4F6)"];n_9805824_81->n_9805824_82[color="blue"];n_9805824_82[label="82: Inode(73KSRBC4ZOXC2):rollup.config.js.noop -> Inode(2SBU4JA2BYW5G)"];n_9805824_82->n_9805824_83[color="blue"];n_9805824_83[label="83: Inode(73KSRBC4ZOXC2):src -> Inode(COXOPYDYKF3XG)"];n_9805824_83->n_9805824_84[color="blue"];n_9805824_84[label="84: Inode(73KSRBC4ZOXC2):static -> Inode(HDJNI6MNYEHJQ)"];n_9805824_84->n_9805824_85[color="blue"];n_9805824_85[label="85: Inode(73KSRBC4ZOXC2):svelte.config.js -> Inode(4LQOKCLW6CKCC)"];n_9805824_85->n_9805824_86[color="blue"];n_9805824_86[label="86: Inode(73KSRBC4ZOXC2):vite.config.js.noop -> Inode(5AIPYQMZK4QXC)"];}}
@import 'sanitize.css';@mixin inset {background: var(--shadow-tertiary)!important;box-shadow: inset 0 0.25rem 0.5rem var(--shadow), inset 0 0.125rem 0.25rem var(--shadow-tertiary), 0 0.0375rem 0 var(--highlight-secondary);}.inset {@include inset;}@mixin zoom_on_hover {transition: all .2s ease-in-out;&:active {transform: scale(0.9) !important;}&:hover {cursor: pointer;transform: scale(1.1);}}.zoom_on_hover {@include zoom_on_hover;}button {border: none;background: none;@include zoom_on_hover;svg {color: var(--text-primary);}.circle {color: var(--text-primary);text-align: center;border: none;width: 3em;height: 3em;border-radius: 50%;}&.primary {background: hsla(240, 100%, 20%, 0.9);}&.secondary {border: 3px solid hsla(240, 100%, 20%, 0.9);}&.big {border-radius: 50vh;text-align: center;color: hsla(100, 25%, 100%, 0.8);margin: 0.375em auto;h2, h3 {font-weight: bold;text-align: center;margin: 0.5em 1em;}}}@mixin text_inputs {background: hsla(50, 50%, 100%, 0.3);border: none;border-radius: 2px;height: 2em;font-family: Montserrat, Tahoma, 'sans-serif';&:focus-visible {outline: none;box-shadow: 0px 0px 3px 3px hsla(240, 100%, 20%, 0.9);}}input {@include text_inputs;}textarea {@include text_inputs;width: 100%;min-height: 3em;}h1 {input {border: none;box-shadow: none;}}main {box-sizing: border-box;width: 100vw;height: calc(100vh - 5rem);margin: 0;padding: 2em;overflow-y: scroll;font-family: Montserrat, Tahoma, 'sans-serif';}nav {position: fixed;bottom: 0;left: 0;right: 0;z-index: 200;padding: 0 2em;background: hsl(0, 100%, 0%);ul {margin: 0 auto;padding: 0;width: fit-content;display: grid;grid-auto-flow: column;gap: 1em;h3 {color: hsla(50, 50%, 100%, 0.3) !important;align-self: center;text-align: center;margin: 0 1em;}li {span {margin: 0;max-height: 5em;display: grid;grid-auto-flow: row;grid-auto-rows: max-content;align-items: center;justify-items: center;color: hsla(50, 50%, 100%, 0.3);text-align: center;text-decoration: none;padding: 1em 0.5em;float: left;}&:hover {svg {fill: hsla(50, 50%, 100%, 1) !important;}span, a {color: hsla(50, 50%, 100%, 1);}}}[aria-current] {position: relative;}[aria-current]::before {position: absolute;content: '';width: calc(100% - 1em);height: 2px;background-color: rgb(255,62,0);display: block;top: 1px;}}}body {background: radial-gradient(circle at top right, hsl(240, 20%, 0%), transparent), radial-gradient(circle at bottom right, hsl(240, 0%, 0%), hsla(240, 100%, 20%, 0.9));margin: 0;}.grid-1 {display: grid;grid-template: 1 1fr / 1 1fr;align-items: center;justify-items: center;}.flex-row {display: flex;flex-direction: row;align-content: center;justify-content: space-around;}.side-scroll {display: grid;grid-auto-flow: column;grid-auto-columns: 80vw;width: 110vw;overflow-x: scroll;scroll-snap-type: x mandatory;justify-items: center;margin-left: -10vw;padding-left: 5vw;&::before {content: '';width: 20vw;}&::after {content: '';width: 20vw;}}.side-scroll-child {width: 80vw;justify-self: center;scroll-snap-align: center;align-content: center;display: grid;}.side-scroll-panel {background: var(--bg-shade);padding: 2rem;border-radius: 0.5rem;opacity: 0.6;transform: scale(0.9);height: 70vh;overflow-y: scroll;}@media (prefers-reduced-motion: no-preference) {.side-scroll-panel {transition: opacity 0.5s ease, transform 0.5s ease;}}li {color: var(--text-primary);}.side-scroll-panel-transition {opacity: 1;transform: none;}.fill-w {width: 100%;}.fill-h {height: 100%;}.fill {width: 100%;height: 100%;}.title-big {font-weight: 700;font-size: 2.8em;text-transform: uppercase;}.lgt-txt {color: hsla(100, 25%, 80%);}.drk-txt {color: hsla(100, 0%, 40%);}h1, h2, h3, p, label {color: var(--text-primary);}p {margin-block-start: 0.25em;margin-block-end: 0.25em;}.panel {padding: 1rem;background: var(--bg-shade);border-radius: 0.5rem;}svg {width: auto;height: 2rem;fill: var(--text-primary);}.opaque-8 {opacity: 0.8;}.panel-opaque {padding: 2rem;background: var(--bg-shade);border-radius: 0.5rem;opacity: 0.8;}.pad-top-05em {padding-top: 0.5em;}.pad-btm-05em {padding-bottom: 0.5em;}.pad-top-btm-05em {padding-top: 0.5em;padding-bottom: 0.5em;}.ctr {margin: auto auto;text-align: center;}.grid-flow-column {display: grid;grid-auto-flow: column;}.height-min-content {height: min-content;}.grid-align-center {align-items: center;}.grid-justify-center {justify-items: center;}.width-100 {width: 100%;}.width-80 {width: 80%;}.width-60 {width: 60%;}.width-40 {width: 40%;}.text-ctr {text-align: center;}.text-rgt {text-align: right;}.text-lft {text-align: left;}.grid {display: grid;}.grid-responsive-columns-1fr {grid-template-columns: repeat(auto-fit, minmax(1rem, 1fr));}.button-circle {display: grid;justify-content: center;text-align: center;width: max-content;grid-template-rows: min-content max-content;border-radius: 50%;}.panel_horizontal {display: grid;grid-auto-flow: column;gap: 3rem;grid-auto-columns: min-content;}.title-row {display: grid;grid-template: 4rem / 4rem auto;align-items: center;svg {justify-self: center;z-index: 200;color:hsla(50, 50%, 100%, 1);}h1 {margin: 0;z-index: 200;width: max-content;}}
<tr><td><label for="email">Email:</label></td><td><input class="width-100" type="text" id="email" bind:value={email} /></td></tr><tr><td><label for="email">Confirm Email:</label></td><td><input class="width-100" type="text" id="email-confirm" bind:value={email_confirm} /></td></tr>{#if typeof message === 'string' && message !== ''}<p>{message}</p>{:else if message === true}<p>OK</p>{/if}<script>let message = ''let email_confirm = ''let email = ''$: checkEmail(email, email_confirm)async function checkEmail() {if (email !== '') {if (email !== email_confirm) {message = 'Emails must match.'}message = 'Checking for existing accounts using that email.'if (await checkServerForExistingEmail(email)) {message = true} else {message = 'That email is already in use, please log in.'}} else {message = ''}}</script>
<div calss="overlay"><Token></Token></div><script>import Token from './_notifications/token.svelte'</script>
<script>$: user_id = datastore.svelte('session/user_id')$: store = datastore.svelte(`state/users/${$user_id}/services/+/+/token`, [], pointer => [pointer.steps[4], pointer.steps[5], pointer.value])$: console.log({S: $store, u: $user_id})$: [service, type, user_token] = $store.length > 0 ? $store[0] : [undefined, undefined, undefined]</script>
<label><slot name="label"></slot><textarea type="text" placeholder="{placeholder}" value={_value} on:keyup={onChange} on:focus={onFocus} on:blur={onBlur}/></label><script>export let event_pathexport let attributeexport let placeholder$: user_id = datastore.svelte('session/user_id')$: store = datastore.svelte(`setup${event_path}/${attribute}`)function onChange({ target }) {const { value } = targetdatastore.queue(`/setup/users/${$user_id}${event_path}/${attribute}`, value)}let _value$: if (!focused) { _value = $store }console.log({_value, placeholder})let focused = falseconst onFocus = () => focused = trueconst onBlur = () => {focused = false_value = $store}</script>
<label><slot name="label"></slot><input type="text" placeholder="{placeholder}" value={_value} on:keyup={onChange} on:focus={onFocus} on:blur={onBlur}/></label><script>export let event_pathexport let attributeexport let placeholder$: user_id = datastore.svelte('session/user_id')$: store = datastore.svelte(`setup${event_path}/${attribute}`)function onChange({ target }) {const { value } = targetdatastore.queue(`/setup/users/${$user_id}${event_path}/${attribute}`, value)}let _value = ''$: if (!focused) {_value = $store}let focused = falseconst onFocus = () => focused = trueconst onBlur = () => {focused = false_value = $store}</script>
<div class="tab"><h3>show:</h3><label><input type="checkbox" bind:group="{include}" value="owned"/><h3>Owned</h3></label><label><input type="checkbox" bind:group="{include}" value="followed"/><h3>Followed</h3></label></div>{#each events as event}<Event path={event}></Event>{/each}<script>import Event from './owned.svelte'$: user_id = datastore.svelte('session/user_id')$: user_owned_events = datastore.svelte(`state/users/${$user_id}/owned`, [])$: followed_events = datastore.svelte(`state/users/${$user_id}/followed`, [])let include = ['owned', 'followed']let events = []$: {let temp = []if (include.includes('owned')) {temp.push(...$user_owned_events)}if (include.includes('followed')) {temp.push(...$followed_events)}events = temp}$: show = datastore.svelte(`session/show_owned_events`, false)</script><style lang="scss">.panel {display: grid;grid-template-columns: 1fr max-content;border-radius: 0;}div {display: grid;grid-auto-flow: column;justify-items: center;}h3 {margin: 0;}.tab {display: grid;grid-auto-flow: column;grid-auto-columns: 1fr;justify-items: center;align-content: center;label {padding-top: 0;padding-bottom: 0;display: inline-flex;gap: 0.5em;}}</style>
{#if $user_owned_events.includes(path)}<Owned path="{path}"></Owned>{:else}<Followed path="{path}"></Followed>{/if}<script>export let pathimport Owned from './owned.svelte'import Followed from './followed.svelte'$: user_id = datastore.svelte('session/user_id')$: user_owned_events = datastore.svelte(`state/users/${$user_id}/owned`, [])</script>
<p style="grid-area: limit;">You own {$user_owned_events.length} Events</p>{#if can_create}<button style="grid-area: add;" on:click|preventDefault={onCreate}>Create</button>{:else}<p>User event limit reached <a>Extend your limit</a></p>{/if}<script>$: user_id = datastore.svelte('session/user_id')$: user_owned_events = datastore.svelte(`state/users/${$user_id}/owned`, [])$: number_of_user_owned_events = $user_owned_events.length$: user_type = datastore.svelte(`state/users/${$user_id}/type`, 'free')$: event_limit = {free: 1,basic: 3,business: 5}[$user_type]$: can_create = number_of_user_owned_events <= event_limitfunction onCreate() {datastore.queue(`/action/users/${$user_id}/events/new`, +new Date)}$: show = datastore.svelte(`session/show_owned_events`, false)function toggle() {datastore.set("/session/show_owned_events", !$show)}</script><style lang="scss">label {justify-content: right;display: grid;grid-template-columns: max-content max-content;border-radius: 0;gap: 2em;}p {text-align: center;}</style>
<div class="panel" on:focus|preventDefault><p>{$name}</p><button on:click={onClick}><Icon name="faPlusSquare" ></Icon></button></div><script>export let event_pathimport Icon from '$lib/components/Icon.svelte'$: name = datastore.svelte(`setup${event_path}/name`)$: user_id = datastore.svelte(`session/user_id`)import { createEventDispatcher } from 'svelte'const dispatch = createEventDispatcher()function onClick() {datastore.push(`/state/users/${$user_id}/followed`, event_path)datastore.push(`/setup${event_path}/users`, event_path)dispatch('close')}</script><style lang="scss">div {display: grid;grid-template-columns: max-content 1fr;button {justify-self: end;margin: 0;}}</style>
<script>import Global from '$lib/components/global.svelte'import Notifications from './_notifications.svelte'</script><main id="main-scroll"><Global><Notifications></Notifications><slot></slot></Global></main>
/*** @type {import('@sveltejs/kit').RequestHandler}*/export async function get({ params }) {const { root, icon_name } = paramslet icontry {switch (root) {case 'solid': {({ [icon_name]: icon } = await import('@fortawesome/free-solid-svg-icons'))break}case 'brands': {({ [icon_name]: icon } = await import('@fortawesome/free-brands-svg-icons'))break}default: {return { status: 404 }}}}catch (error) {return { status: 404 }}if (icon) {return {headers: {'content-type': 'application/javascript'},body: JSON.stringify(icon)}}}
let global_windowif (typeof window !== 'undefined') {global_window = window} else {global_window = global}export const unsubscribe = (topic) => {// console.log('subscribe()', 'topic:', topic)let operation = 'u'let payloadpayload = {o: operation,t: topic}const ws = global_window.connections['node']if (ws) {ws.send(payload)}}
<section class="modal_wrapper" in:fade="{{ duration: 300 }}" out:fade|local="{{ delay: 300, duration: 300 }}"><div class="modal_bg_blur" on:click|preventDefault="{close}"></div><div class="card modal" in:fly="{{ delay: 300, y: 300, duration: 300 }}" out:fly="{{ y: 300, duration: 300 }}"><div class="modal_header"><slot name="header"></slot><button class="close" on:click|preventDefault="{close}"><Icon name="faTimes" /></button></div><div class="modal_body"><slot></slot></div></div></section><script>import { fade, fly } from 'svelte/transition'import Icon from '$lib/components/Icon.svelte'import { createEventDispatcher } from 'svelte'const dispatch = createEventDispatcher()const close = () => {dispatch('close')}</script><style lang="scss">.modal_wrapper {position: fixed;width: 100%;height: 90%;z-index: 1000;display: grid;grid-template:'main' 1fr / 1fr;margin-top: -2rem;margin-left: -2rem;place-items: center;@media (min-width: 769px) {// Breakpoint: tablet to desktopwidth: 100vw;}.modal_bg_blur {width: 100%;height: 100%;display: grid;grid-area: main;z-index: 2;backdrop-filter: blur(3px);background: var(--bg-darken);}.modal {width: 60%;height: max-content;min-height: 25vh;grid-area: main;z-index: 3;overflow: hidden;padding: 1rem;grid-template-rows: max-content 1fr;background: var(--bg-button);.modal_header {padding: 0rem 0.5rem;grid-column: 1;grid-column-end: -1;grid-template-columns: 1fr max-content;display: grid;align-items: center;button {justify-self: end;place-items: center;display: grid;}}.modal_body {display: grid;grid-auto-rows: max-content;gap: 1rem;padding: 0.5rem;overflow-y: scroll;overflow-x: hidden;}}}button {background: none;border: none;}.modal_body::-webkit-scrollbar {width: 0.375rem;display: block;background: transparent;border-radius: 50px;}.modal_body::-webkit-scrollbar-thumb {background: linear-gradient(0deg, var(--accent), var(--accent-secondary));border-radius: 50px;}</style>
<script>import { faCaretUp, faCaretDown, faCaretRight, faCheck, faCircle, faExclamationTriangle, faTrash, faStickyNote, faWrench, faIgloo, faSearch, faUser, faRecordVinyl, faChartBar, faFolderOpen, faAngleDoubleLeft, faGuitar, faCompass, faSignOutAlt, faInfo } from '@fortawesome/free-solid-svg-icons'import { faSpotify, faApple, faCanadianMapleLeaf } from '@fortawesome/free-brands-svg-icons'export let name = 'dot'let className = ''export { className as class }export let found = trueexport let scale = 1// font awesome properties are take as additional props via metalet widthlet heightlet pathlet labellet box = `0 0 0 0`let styleconst solid_icons = {caret_up: faCaretUp,caret_down: faCaretDown,caret_right: faCaretRight,check: faCheck,circle: faCircle,exclamation_triangle: faExclamationTriangle,trash: faTrash,note: faStickyNote,wrench: faWrench,info: faInfo,igloo: faIgloo,search: faSearch,user: faUser,chart: faChartBar,folder: faFolderOpen,angle_double_left: faAngleDoubleLeft,guitar: faGuitar,compass: faCompass,record: faRecordVinyl,'sign-out': faSignOutAlt}const brand_icons = {spotify: faSpotify,apple: faApple,maple_leaf: faCanadianMapleLeaf}const classEval = (className, svgName) => {if (className != '') {if (solid_icons[svgName]) {found = truereturn [solid_icons[svgName], className]} else if (brand_icons[svgName]) {found = truereturn [brand_icons[svgName], className]} else {found = falsereturn [faCircle, className]}} else if (solid_icons[svgName]) {found = truereturn [solid_icons[svgName],solid_icons[svgName].prefix + ' ' + solid_icons[svgName].iconName]} else if (brand_icons[svgName]) {found = truereturn [brand_icons[svgName],brand_icons[svgName].prefix + ' ' + brand_icons[svgName].iconName]} else {found = falsereturn [ faCircle, 'fas fa-circle' ]}}let [data, svgClassName] = classEval(className, name)$: [data, svgClassName] = classEval(className, name)const propEval = props => {const entries = Object.entries(props)return entries.reduce((result, [key, value]) => {if (['class', 'name', 'found', 'scale'].includes(key)) {return result}if (value === true){result.push('fa-' + key)} else if (value !== false) {result.push('fa-' + key +'-' + value)}return result}, []).join(' ')}let props = propEval($$props)$: props = propEval($$props)$: {const [_width, _height /* _ligatures */ /* _unicode */, , , _svgPathData] = data.iconwidth = _widthheight = _heightpath = _svgPathDatalabel = data.iconNamebox = `0 0 ${width} ${height}`style = `font-size: ${scale}em`}</script><svgversion="1.1"class="fa-icon {className} {props}"x={0}y={0}{width}{height}data-icon={name}aria-label={label}role={label ? 'img' : 'presentation'}viewBox={box}{style}><path d={path} /></svg><style type="text/scss">svg.fa-spin {-webkit-animation-name: spin;-moz-animation-name: spin;-ms-animation-name: spin;animation-name: spin;-webkit-animation-duration: 4000ms;-moz-animation-duration: 4000ms;-ms-animation-duration: 4000ms;animation-duration: 4000ms;-webkit-animation-timing-function: linear;-moz-animation-timing-function: linear;-ms-animation-timing-function: linear;animation-timing-function: linear;-webkit-animation-iteration-count: infinite;-moz-animation-iteration-count: infinite;-ms-animation-iteration-count: infinite;animation-iteration-count: infinite;}@-moz-keyframes spin {from {-moz-transform: rotate(0deg);}to {-moz-transform: rotate(360deg);}}@-webkit-keyframes spin {from {-webkit-transform: rotate(0deg);}to {-webkit-transform: rotate(360deg);}}@keyframes spin {from {transform:rotate(0deg);}to {transform:rotate(360deg);}}</style>
server {server_name processor.djinmusic.ca www.processor.djinmusic.ca;location / {proxy_pass http://127.0.0.1:25706;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection $connection_upgrade;proxy_set_header Host $host;}}
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=480m use_temp_path=off;map $http_upgrade $connection_upgrade {default upgrade;'' close;}server {listen 80;server_name djinmusic.ca www.djinmusic.ca;}server {listen 443 ssl;listen [::]:443 ssl;server_name djinmusic.ca www.djinmusic.ca;location / {return 301 https://app.$host$request_uri;}}
server {server_name app.djinmusic.ca www.app.djinmusic.ca;location / {proxy_cache my_cache;proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;proxy_cache_revalidate on;proxy_pass http://localhost:5443;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection 'upgrade';proxy_set_header Host $host;proxy_cache_bypass $http_upgrade;}}
server {server_name admin.djinmusic.ca www.admin.djinmusic.ca;location / {proxy_cache my_cache;proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;proxy_cache_revalidate on;proxy_pass http://localhost:6443;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection 'upgrade';proxy_set_header Host $host;proxy_cache_bypass $http_upgrade;}}
module.exports = {parser: 'babel-eslint',parserOptions: {ecmaVersion: 2019,sourceType: 'module',ecmaFeatures: {modules: true}},env: {es2020: true,browser: true,mocha: true,node: true},extends: ['eslint:recommended', 'prettier'],plugins: ['svelte3', 'html', 'optimize-regex', 'prettier'],overrides: [{files: ['*.svelte'],processor: 'svelte3/svelte3'}],rules: {'id-blacklist': ['warn', 'cb', 'err', 'req', 'res'],'optimize-regex/optimize-regex': 'off','prettier/prettier': ['off', { singleQuote: true, semi: false, printWidth: 256 }],'no-console': 'off','no-debugger': 'warn','no-fallthrough': 'off','no-undef': 'warn','no-unused-vars': ['error', { args: 'none' }]},settings: {'svelte3/ignore-warnings': warning => {let ignore_warning = falseswitch (warning.code) {case 'unused-export-let':ignore_warning = truebreakdefault:break}return ignore_warning},'svelte3/ignore-styles': () => true},globals: {CONFIG: true,_: true,__$$self: true,connections: true,datastore: true,Pointer: true,theme: true,moment: true,DateTime: true,Duration: true,Interval: true,requireYAML: true,system_uuid: true,system_path: true,system_topic: true}}
.reify-cache/dist/node_modules/tmp/
.git*.lognode_modulesapp/djiny/srcapp/djiny/cypressapp/djiny/cypress.json.nginx.bin__sapper__/dev
#!/usr/bin/env bash. $BIN_DIR/_lib.shecho '🔎 Linting ...'# Lint JavaScriptJS_ERROR_CODE=0FILES=$(find $WORKING_DIR \( -name '*.js' -o -name '*.html' \) -not -path "$WORKING_DIR/.reify-cache/*" -not -path "$WORKING_DIR/node_modules/*" -not -path "$WORKING_DIR/dist/*" -not -path "$WORKING_DIR/tmp/*")if [[ "${FILES:-x}" != "x" ]]; thenecho -e " $(echo "$FILES" | wc -l | awk '{print $1}') JavaScript, HTML, and Svelte files"set +eeslint -c "$MONO_DIR/.eslintrc.js" --ignore-path "$MONO_DIR/.eslintignore" $FILESJS_ERROR_CODE=$?set -efi# Lint SCSSCSS_ERROR_CODE=0FILES=$(find $WORKING_DIR \( -name '*.scss' -o -name '*.scss' -o -name '*.html' \) -not -path "$WORKING_DIR/.reify-cache/*" -not -path "$WORKING_DIR/node_modules/*" -not -path "$WORKING_DIR/dist/*" -not -path "$WORKING_DIR/tmp/*")if [[ "${FILES:-x}" != "x" ]]; thenecho -e " $(echo "$FILES" | wc -l | awk '{print $1}') CSS files"set +estylelint --fix --ignore-path "$MONO_DIR/.stylelintignore" $FILESCSS_ERROR_CODE=$?set -efiif [[ $JS_ERROR_CODE -gt 0 ]] || [[ $CSS_ERROR_CODE -gt 0 ]]; thenexit 1fi
#!/usr/bin/env bash. $BIN_DIR/_lib.shgecho 'Installing repo-level dependencies...'cd $MONO_DIRpnpm install -rgecho 'Installing app dependencies...'cd $MONO_DIR/apppnpm install -rgecho 'Installing channel dependencies...'cd $MONO_DIR/services/channelpnpm install -rgecho 'Installing datastore dependencies...'cd $MONO_DIR/services/datastorepnpm install -r
#!/usr/bin/env bash. $BIN_DIR/_lib.shecho '🗄 Formatting ...'# Format *.js filesformat-js() {FILES=""if [[ ! -z ${@+x} ]]; then_IFS=$IFSIFS=" "FILES=${@}elseFILES=$(find $WORKING_DIR -name '*.js' -not -path "$WORKING_DIR/.reify-cache/*" -not -path "$WORKING_DIR/node_modules/*" -not -path "$WORKING_DIR/dist/*" -not -path "$WORKING_DIR/tmp/*")fi[[ "${FILES:-x}" = "x" ]] && returnecho -e " $(echo "$FILES" | wc -l | awk '{print $1}') JavaScript files"prettier --config "$MONO_DIR/.prettierrc" --ignore-path "$MONO_DIR/.prettierignore" --write $FILES >/dev/nullif [[ ! -z ${_IFS+x} ]]; thenIFS=$_IFSfi}# Format *.html <script> and <style> elementsformat-html() {echo "Formatting html and svelte has been disabled."return 0FILES=""if [[ ! -z ${@+x} ]]; then_IFS=$IFSIFS=" "FILES=${@}elseFILES=$(find $WORKING_DIR -name '*.svelte' -not -path "$WORKING_DIR/node_modules/*" -not -path "$WORKING_DIR/dist/*" -not -path "$WORKING_DIR/tmp/*")fi[[ "${FILES:-x}" = "x" ]] && returnecho -e " $(echo "$FILES" | wc -l | awk '{print $1}') Svelte files"prettier --config "$MONO_DIR/.prettierrc" --ignore-path "$MONO_DIR/.prettierignore" --write $FILES >/dev/nullif [[ ! -z ${_IFS+x} ]]; thenIFS=$_IFSfi}format-all() {format-jsformat-html}ARGC=$#if [[ $ARGC -gt 1 ]]; thenARGV=( $@ )COMMAND=${ARGV[0]}FILES=${ARGV[@]:1}case $COMMAND in"js" )format-js $FILES;;"html" )format-html $FILES;;* )format-all;;esacelseformat-allfi
#!/usr/bin/env bash. $BIN_DIR/_lib.sh#npm run buildprintf "Building Public App\n\n"cd $MONO_DIR/app/djinynpm run buildcd $MONO_DIR/app/admincd $MONO_DIRprintf "Using $HOME/.ssh/id_rsa_corda_digital_ocean\n\n"printf "\n1. Deploying updated app changes\n\n"rsync --progress -Pavuz --exclude-from="$BIN_DIR/rsync-deploy.ignore" -e "ssh -i $HOME/.ssh/id_rsa_corda_digital_ocean" "${MONO_DIR}/." "tpcowan@djinmusic.ca:/home/tpcowan/djinmusic"printf "\n2. Deploying updated app changes\n\n"rsync --progress -Pavuz -e "ssh -i $HOME/.ssh/id_rsa_corda_digital_ocean" "${MONO_DIR}/.nginx/sites-available/" "tpcowan@djinmusic.ca:/etc/nginx/sites-available"printf "\n3. Updating remote deploy script\n$BIN_DIR/deploy-remote.sh\n\n"rsync --progress -Pavuz -e "ssh -i $HOME/.ssh/id_rsa_corda_digital_ocean" "${BIN_DIR}/deploy-remote.sh" "tpcowan@djinmusic.ca:/home/tpcowan/djinmusic/deploy-remote.sh"printf "\n4. Triggering remote deploy script\n\n"ssh -i $HOME/.ssh/id_rsa_corda_digital_ocean -t tpcowan@djinmusic.ca < $BIN_DIR/deploy-remote.sh
#!/bin/bashPATH=/home/tpcowan/.nvm/versions/node/v12.18.3/bin:$PATHif [ "$(uname -s)" == "Darwin" ]; thenecho "Don't run this on your local computer!"exit 1fiecho "[remote] linking nginx configuration"sudo ln -s /etc/nginx/sites-available/djinlist/* /etc/nginx/sites-enabled/sudo nginx -s reloadecho "[remote] Updating processor"cd djinmusicpnpm install -recho "[remote] Checking for djinlist-server"server_check=$(pm2 pid djinlist-server)echo "${server_check}"echo "${#server_check}"if [ ${#server_check} -eq 0 ]; thenecho "[remote] Starting djinlist-server"cd services/channelpm2 start node --name "djinlist-server" -l "../../djinlist-server.log" -- --experimental-modules --experimental-json-modules --es-module-specifier-resolution=node src/index.jscd ../..elseecho "[remote] Restarting djinlist-server"pm2 stop djinlist-serverpm2 start djinlist-serverfiecho "[remote] Checking for djinlist-app"djiny_check=$(pm2 pid djinlist-app)echo "${djiny_check}"echo "${#djiny_check}"if [ ${#djiny_check} -eq 0 ]; thenecho "[remote] Starting djinlist-app"cd app/djinypm2 start npm --name "djinlist-app" -l "../../djinlist-app.log" -- startcd ../..elseecho "[remote] Restarting djinlist-app"pm2 stop djinlist-apppm2 start djinlist-appfiecho "[remote] Checking for djinlist-admin"admin_check=$(pm2 pid djinlist-admin)echo "${admin_check}"echo "${#admin_check}"if [ ${#admin_check} -eq 0 ]; thenecho "[remote] Starting djinlist-admin"cd app/adminpm2 start npm --name "djinlist-admin" -l "../../djinlist-admin.log" -- startcd ../..elseecho "[remote] Restarting djinlist-admin"pm2 stop djinlist-adminpm2 start djinlist-adminfiecho "[remote] Installed"
set -euo pipefailIFS=$'\n\t'gecho() {local msg=$@echo -e "\033[0;32m${msg}\033[0m"}recho() {local msg=$@echo -e "\033[0;31m${msg}\033[0m"}yecho() {local msg=$@echo -e "\033[0;93m${msg}\033[0m"}
node_modules/