Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Refined arch #4

Merged
merged 22 commits into from
May 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b2a8a31
ui config parseFields exclude sub-keys from list if main key has list…
jankapunkt May 14, 2020
678dd70
ui config parseActions implemented inner-document field dependencies
jankapunkt May 14, 2020
66f05fa
ui config extracted to formSchema into own file
jankapunkt May 15, 2020
207feef
added utils cloneObject#
jankapunkt May 15, 2020
81729c5
added ui config getValueFunction
jankapunkt May 15, 2020
5fdd561
ui list updated
jankapunkt May 15, 2020
62cb8fc
ui config getLabel added
jankapunkt May 15, 2020
69569c7
renamed BackendConfig to ServiceRegistry and moved it to own package
jankapunkt May 15, 2020
c274881
ui nav overflow fixed
jankapunkt May 18, 2020
8cb97e9
ui config getValueFunction use better error messages
jankapunkt May 18, 2020
434104d
ui list remove debugger
jankapunkt May 18, 2020
e1c1c58
api method factory added permission mixin
jankapunkt May 18, 2020
3f1a9eb
api Apps decoupled from ServiceRegistry and i18n
jankapunkt May 18, 2020
03e2dd0
ui config added settings form and schem
jankapunkt May 18, 2020
26800c5
decoupled all functions from ServiceRegistry into standalone
jankapunkt May 18, 2020
80a4d52
extracted create-route functions from ServiceRegistry into standalone
jankapunkt May 18, 2020
2066364
client with decoupled startup procedures
jankapunkt May 18, 2020
aa1c5d5
ui renamed generic to views
jankapunkt May 18, 2020
9a0d26b
ui moved config out of generic folder
jankapunkt May 18, 2020
663d8a9
api ViewTypes added
jankapunkt May 18, 2020
db4c051
FOrmTypes added
jankapunkt May 18, 2020
c8a0ff6
ui config wrappers parseSettings added
jankapunkt May 18, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .meteor/packages
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ shell-server@0.5.0 # Server-side component of the `meteor shell` comm
# Basic tools
leaonline:errors
leaonline:utils
leaonline:interfaces
leaonline:corelib
leaonline:service-registry

# collections
aldeed:collection2
Expand Down
2 changes: 1 addition & 1 deletion .meteor/versions
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ leaonline:corelib@1.0.0
leaonline:ddp-login@1.0.4
leaonline:errors@1.0.0
leaonline:files-collection-factory@1.0.0
leaonline:interfaces@1.0.0
leaonline:method-factory@1.0.0
leaonline:oauth-lea@1.0.2
leaonline:ratelimit-factory@1.0.0
leaonline:service-registry@1.0.0
leaonline:utils@1.0.0
livedata@1.0.18
localstorage@1.2.0
Expand Down
1 change: 1 addition & 0 deletions client/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import '../imports/startup/client/language'

// startup routines
import '../imports/startup/client/routes'
import '../imports/startup/client/reloadRoute'
import '../imports/startup/client/apps'
import '../imports/startup/client/fontawesome'
import '../imports/startup/client/bootstrap'
Expand Down
256 changes: 94 additions & 162 deletions imports/api/apps/Apps.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
/* global localStorage */
import { Meteor } from 'meteor/meteor'
import { Tracker } from 'meteor/tracker'
import { DDP } from 'meteor/ddp-client'
import { ReactiveDict } from 'meteor/reactive-dict'
import { i18n } from '../i18n/I18n'
import { BackendConfig } from '../config/BackendConfig'
import { onServer } from '../../utils/arch'
import { getConfigTypeOptions } from '../config/getConfigTypeOptions'
import { getFormTypeOptions } from '../../ui/forms/getFormTypeOptions'

export const Apps = {
name: 'apps',
Expand All @@ -14,180 +9,87 @@ export const Apps = {
debug: false
}

const _apps = new ReactiveDict()
const _connections = {}
const _trackers = {}
// Note, that the schema is normalized to map one context to one document.
// Registering an app may result in multiple context documents at once.

function connect (name, url) {
if (!_connections[name]) {
_connections[name] = DDP.connect(url)
}
return _connections[name]
}

function updateStatus (name, status) {
const app = _apps.get(name)
if (!app) {
throw new Error(`[Apps] expected app by name ${name}`)
}
app.status = status
_apps.set(name, app)
}

function updateLogin (name, userId) {
const app = _apps.get(name)
app.login = { successful: !!userId }
_apps.set(name, app)
}

function updateConfig (name, config) {
const app = _apps.get(name)
app.config = config
_apps.set(name, app)
}

function log (...args) {
if (Apps.debug && Meteor.isDevelopment) {
console.info('[Apps]', ...args)
}
}

function track (name, connection, ddpLogin) {
const url = connection._stream.rawUrl
_trackers[name] = Tracker.autorun(() => {
// skip this computation if there is
// currently no logged in backend user
if (Meteor.status().connected && !Meteor.user() && !Meteor.userId()) {
// clear localStorage entries from previous
// login results to avoid follow-up 403 errors
localStorage.removeItem(`${url}/lea/userId`)
localStorage.removeItem(`${url}/lea/loginToken`)
localStorage.removeItem(`${url}/lea/loginTokenExpires`)
// logout connection if still connected
if (connection.userId()) {
connection.call('logout')
}
return
Apps.schema = {
// name of the app that registered to the backend
name: {
type: String,
autoform: {
disabled: true
}
},

// always update status to
// trigger reactive Template updates
const status = connection.status()
updateStatus(name, status)

// also skip if we are not yet connected
if (!status.connected) {
log(url, 'not yet connected -> skip')
return
// name of the context that has been registered
context: {
type: String,
autoform: {
disabled: true
}

// skip if we have not explcitly enabled the DDP login
if (!ddpLogin) {
return
},

// edit log / history flag
useHistory: {
type: Boolean,
defaultValue: true
},

// comments flag
useComments: {
type: Boolean,
defaultValue: true
},

// flag if we want to track links to this context
useDependencyTracking: {
type: Boolean,
defaultValue: true
},

// which template to use as main template for overview
template: {
type: String,
autoform: {
options: getConfigTypeOptions
}
},

// update userId and skip,
// if we are already logged-in
const userId = connection.userId()
const loggingIn = connection._loggingIn
updateLogin(name, userId)
if (userId || loggingIn) return

log(url, 'get credentials')
connection._loggingIn = true
Apps.methods.getServiceCredentials.call((err, credentials) => {
if (err || !credentials) {
connection._loggingIn = false
console.error(err)
log(url, 'no credentials received, skip login')
return
}
log(url, 'init login')
const options = { accessToken: credentials.accessToken, debug: Apps.debug }
DDP.loginWithLea(connection, options, (err, res) => {
connection._loggingIn = false
if (err) {
return console.error(err)
} else {
log(url, 'logged in with token', !!res)
configure(name)
}
})
})
})
}
// define fields-specific settings
fields: Array,
'fields.$': Object,

const configure = function (name) {
const app = Apps.get(name)
const { url } = app
const { connection } = app
const lang = i18n.getLocale()

connection.call(BackendConfig.methods.get.name, { lang }, (err, config) => {
log(url, 'backend config received', config)
if (err) return console.error(err)
if (config) {
BackendConfig.parse(config)
BackendConfig.parent(name, config)
i18n.add(lang, config.lang)
updateConfig(name, config)
BackendConfig.children(name, config)
hostLoaded(name, null, true)
// field name / key (not label)
'fields.$.name': {
type: String,
autoform: {
disabled: true
}
})
}
},

const callbacks = new Map()
// may it appear in lists
'fields.$.inList': Boolean,

Apps.onHostLoaded = function (name, cb) {
const hostCbs = callbacks.get(name) || []
hostCbs.push(cb)
callbacks.set(name, hostCbs)
}
// may it appear in summaries and previews
'fields.$.inSummary': Boolean,

function hostLoaded (name, err, res) {
const loadedCbs = callbacks.get(name)
if (!loadedCbs || loadedCbs.length === 0) {
return
}
'fields.$.inForm': Boolean,

loadedCbs.forEach(cb => {
setTimeout(() => cb(err, res), 0)
})

loadedCbs.length = 0
}

Apps.register = function ({ name, label, url, icon, ddpConnect, ddpLogin }) {
const app = _apps.set(name, { name, label, url, icon, ddpConnect, ddpLogin })
if (ddpConnect) {
const connection = connect(name, url)
track(name, connection, ddpLogin)
}
return app
}

Apps.get = function (name) {
const app = _apps.get(name)
const connection = _connections[name]
return Object.assign({}, app, { connection })
}

Apps.connection = function (name) {
return _connections[name]
}

Apps.all = function () {
const all = _apps.all()
return all && Object.values(all)
// is there a specific autoform formType to use
'fields.$.form': {
type: String,
optional: true,
autoform: {
options: getFormTypeOptions
}
},
}

Apps.methods = {}

Apps.methods.getServiceCredentials = {
name: 'apps.methods.getServiceCredentials',
schema: {},
isPublic: true, // fixme,
numRequests: 5,
timeInterval: 500,
run: onServer(function () {
Expand All @@ -198,3 +100,33 @@ Apps.methods.getServiceCredentials = {
Meteor.call(Apps.methods.getServiceCredentials.name, cb)
}
}

Apps.methods.updateSettings = {
name: 'apps.methods.updateSettings',
schema: Apps.schema,
numRequests: 1,
timeInterval: 1000,
run: onServer(function (settingsDoc) {
const existingDocId = Apps.collection().findOne({ name: settingsDoc.name, context: settingsDoc.context })
if (existingDocId) {
return Apps.collection().update(existingDocId, { $set: settingsDoc })
} else {
return Apps.collection().insert(settingsDoc)
}
})
}

Apps.publications = {}

Apps.publications.getByNames = {
name: 'apps.publications.getByNames',
schema: {
names: Array,
'names.$': String
},
numRequests: 5,
timeInterval: 500,
run: onServer(function ({ names = [] }) {
return Apps.collection().find({ name: { $in: names } })
}),
}
Loading