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
- VSCode (https://code.visualstudio.com/) or your preferred IDE
- Dotnet Core 3.1 (https://dotnet.microsoft.com/en-us/download/dotnet/3.1)
- Azure Functions Core Tools(https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local)
cd DDD.Functions && func host start --build --debug --verbose
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 agendaGetAgendaSchedule
: C# Azure Function that returns agenda schedule from sessionizeGetSubmissions
: 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
StopSyncingSessionsFrom
: this is when we should stop syncing sessions from Sessionize, usually CFP close dateStopSyncingAgendaFrom
: this is when we should stop syncing agenda from Sessionize, usually conference dateStopSyncingTitoFrom
: this is when we should stop syncing tickets holder information from Tito usually the date before conference dateVotingAvailableFrom
: voting start dateVotingAvailableTo
: voting end dateSubmissionsAvailableFrom
: this is when we can retrieve submitted submissions for internal usage and voting, usually it is the when voting opensSubmissionsAvailableTo
: this is when we cannot retrieve submitted submissions, usually it is when Agenda is publishedStartSyncingAppInsightsFrom
: this is when we start collecting insights for voting and submissions, usually when CFP opensStopSyncingAppInsightsFrom
: this is when we stop collecting insights for voting and submissions, usually when voting closesFeedbackAvailableFrom
: this is when we start accepting feedback, usually the conference start date at 8:00amFeedbackAvailableTo
: this is when we stop accepting feedback, usually the conference start date at 5:00pm
The backend application depends on programmatic access to the Frontend Website's Application Insights to pull and store information on voting behavior.
To supply this access, create an API key with Read telemetry
permissions within the frontend website's Application Insights instance in the Azure Portal, and enter the Application ID and Key presented into the AppInsightsApplicationId
and AppInsightsApplicationKey
parameters.
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