This project contains backend functionality to run the DDD conferences, including:
- Syncing data from Sessionize to Azure Table Storage (tenanted by conference year) for submitted sessions (and submitters) and separate to that, selected sessions (and presenters)
- APIs that return submission and session (agenda) information during allowed times
- APIs to facilitate voting by the community against (optionally anonymous) submitted sessions (notes stored to Azure Table Storage tenanted by conference year) including various mechanisms to detect fraud
- Syncing Tito order IDs and Azure App Insights voting user IDs to assist with voting fraud detection and validation
- API to return analysed voting information
- Ability to trigger an Azure Logic App when a new session is detected from Sessionize (which can then be used to create Microsoft Teams / Slack notifications for visibility to the organising committee and/or to trigger moderation actions)
- Tito webhook to take order notifications, de-duplicate them and place them in queue storage so they can be picked up by a Logic App (or similar) to do things like create Microsoft Teams / Slack notifications for visibility to the organising committee
- Getting feedback information and prize draw names
DDD.Core
: Cross-cutting logic and core domain modelDDD.Functions
: Azure Functions project that contains:AppInsightsSync
: C# Azure Function that syncs app insights user IDs to Azure Table Storage for users that submitted a voteTitoNotification
: Node.js Azure Function that exposes a web hook URL that can be added to Tito (via Account Settings > Webhooks) for theorder.placed
action that will then de-duplicate webhook events and push them to queue storage for further processing (e.g. via a Logic App)TitoSync
: C# Azure Function that syncs Tito order IDs to Azure Table Storage for a configured eventGetAgenda
: C# Azure Function that returns sessions and presenters that have been approved for agendaGetSubmissions
: C# Azure Function that returns submissions and submitters for use with either voting or showing submitted sessionsGetVotes
: C# Azure Function that returns analysed vote information; can be piped into Microsoft Power BI or similar for further processing and visualisationNewSessionNotification
: C# Azure Function that responds to new submissions in Azure Table Storage and then calls a Logic App Web Hook URL (from config) with the session and presenter information (marking that session as notified to avoid duplicate notifications)SessionizeReadModelSync
: C# Azure Function triggered by a cron schedule defined in config that performs a sync from Sessionize to Azure Table Storage for submissionsSessionizeAgendaSync
: C# Azure Function triggered by a cron schedule defined in config that performs a sync from Sessionize to Azure Table Storage for approved sessionsSubmitVote
: : C# Azure Function that allows a vote for submissions to be submitted, where it is validated and persisted to Azure Table Storage
DDD.Sessionize
: Syncing logic to sync data from sessionize to Azure Table StorageDDD.Sessionize.Tests
: Unit tests for the Sessionize Syncing codeinfrastructure
: Azure ARM deployment scripts to provision the backend environmentDeploy-Local.ps1
: Run locally to debug or develop the scripts using your user context in AzureDeploy.ps1
: Main deployment script that you need to call from CD pipelineazuredeploy.json
: Azure ARM template
.vsts-ci.yml
: VSTS Continuous Integration definition for this project
VSTS doesn't yet support .yml files for Continuous Delivery (Release) so the steps to set it up are:
- Install SAS Token VSTS extension
- todo: Just add it to Deploy.ps1
- Create the release definition triggered by the CI build
- Add a task for the SAS token generation for the storage account you persisted deployments to set the output variables to
DeploymentZipUri
andDeploymentZipToken
respectively, recommend setting the timeout to a big number so it's always available (e.g.1000000
), permission should just ber
- Add an Azure PowerShell task against your subscription and:
$(System.DefaultWorkingDirectory)/{CI build name}/infrastructure/Deploy.ps1
as the script file path- `` as the script arguments
- Add variables, e.g.:
- Profit!
The NewSessionNotificationLogicAppUrl
value is gotten by creating a logic app and copying the webhook URL from it. The logic app would roughly have:
-
When a HTTP request is received
trigger with json schema of:{ "properties": { "Presenters": { "items": { "properties": { "Bio": { "type": "string" }, "ExternalId": { "type": "string" }, "Id": { "type": "string" }, "Name": { "type": "string" }, "ProfilePhotoUrl": { "type": "string" }, "Tagline": { "type": "string" }, "TwitterHandle": { "type": "string" }, "WebsiteUrl": { "type": "string" } }, "required": [ "Id", "ExternalId", "Name", "Tagline", "Bio", "ProfilePhotoUrl", "WebsiteUrl", "TwitterHandle" ], "type": "object" }, "type": "array" }, "Session": { "properties": { "Abstract": { "type": "string" }, "CreatedDate": { "type": "string" }, "ExternalId": { "type": "string" }, "Format": { "type": "number" }, "Id": { "type": "string" }, "Level": {}, "MobilePhoneContact": {}, "PresenterIds": { "items": { "type": "string" }, "type": "array" }, "Tags": { "type": "array" }, "Title": { "type": "string" } }, "type": "object" } }, "type": "object" }
-
For each
action againstPresenters
with a nestedCompose
action againstName
-
Post message
action (for Teams/Slack) with something like@{join(actionOutputs('Compose'), ', ')} submitted a talk '@{triggerBody()?['Session']['Title']}' as @{triggerBody()?['Session']['Format']} / @{triggerBody()?['Session']['Level']} with tags @{join(triggerBody()?['Session']['Tags'], ', ')}.
-
Send an email
action (for O365/GMail/Outlook.com depending on what you have) that sends an email if the previous step failed (viaConfigure run after
)
The logic app would roughly have:
When there are messages in a queue
trigger to theattendees
queue of the{conferencename}functions{environment}
storage accountPost message
action (for Teams/Slack) with something like@{json(trigger().outputs.body.MessageText).name} is attending @{json(trigger().outputs.body.MessageText).event} as @{json(trigger().outputs.body.MessageText).ticketClass} (orderid: @{json(trigger().outputs.body.MessageText).orderId}). @{json(trigger().outputs.body.MessageText).qtySold}/@{json(trigger().outputs.body.MessageText).totalQty} @{json(trigger().outputs.body.MessageText).ticketClass} tickets taken.
Delete message
action for theattendees
queue with the Message ID and Pop Receipt from the trigger