├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── interviews ├── refactor.cp.js └── refactor.js ├── package.json ├── schemas ├── aliasRedirects.sql ├── anchors.sql ├── answers.sql ├── changeLogs.sql ├── contentRequests.sql ├── discussionSubscriptions.sql ├── domainFriends.sql ├── domainMembers.sql ├── domains.sql ├── feedPages.sql ├── helpers │ ├── changes.sql │ ├── create_reqs.sql │ └── create_reqs2.sql ├── invites.sql ├── lastViews.sql ├── lastVisits.sql ├── lenses.sql ├── likeableIds.sql ├── likes.sql ├── links.sql ├── maintainerSubscriptions.sql ├── marks.sql ├── pageInfos.sql ├── pagePairs.sql ├── pageSummaries.sql ├── pageToDomainSubmissions.sql ├── pages.sql ├── pathInstances.sql ├── pathPages.sql ├── projects.sql ├── redLinks.sql ├── searchStrings.sql ├── updates.sql ├── userMasteryPairs.sql ├── userPageObjectPairs.sql ├── userRequisitePairSnapshots.sql ├── userSubscriptions.sql ├── users.sql ├── visits.sql └── votes.sql ├── scripts ├── build_gomon.sh ├── check_deps.go ├── configs_identical.sh ├── create_daemon_user.sh ├── create_db.sh ├── create_monitoring_vm.sh ├── create_pages.sh ├── db_shell.sh ├── decrypt_config.sh ├── decrypt_config_remote.sh ├── deploy_ae.sh ├── deploy_queue.sh ├── encrypt_config.sh ├── file_watcher.patch ├── gcloud_bashrc.sh ├── gcloud_bootstrap_init.sh ├── import_keys.sh ├── init.sh ├── local_mysql.sh ├── needs_gofmt.sh ├── patch_appengine.sh ├── pre-commit.sh ├── pre-push.sh ├── run_gomon.sh ├── run_monitoring.sh ├── run_prober.sh ├── run_tests.sh ├── setup_valid_db.sh ├── stage.sh ├── style_js.sh ├── symlink_git_hooks.sh ├── update_db.sh ├── update_vm_manifest.sh └── v2_stage.sh ├── src ├── .vscode │ └── settings.json ├── config │ ├── config.go │ ├── config_test.go │ └── init.go ├── core │ ├── constants.go │ ├── currentUser.go │ ├── databaseUtils.go │ ├── domains.go │ ├── feed.go │ ├── lastViewHelpers.go │ ├── likeables.go │ ├── page.go │ ├── pageLoadOptions.go │ ├── pagePair.go │ ├── pageUtils.go │ ├── permission.go │ ├── update.go │ └── user.go ├── database │ ├── database.go │ ├── dbcore │ │ └── core.go │ └── queryPart.go ├── elastic │ └── elastic.go ├── facebook │ └── facebook.go ├── logger │ └── logging.go ├── mailchimp │ └── mailchimp.go ├── okta │ └── okta.go ├── pages │ └── pages.go ├── queue_daemon │ ├── init.go │ └── module.yaml ├── sessions │ ├── context.go │ ├── creds.go │ ├── error.go │ └── init.go ├── site │ ├── adminTaskHandler.go │ ├── app.yaml │ ├── approveCommentHandler.go │ ├── approvePageEditProposalHandler.go │ ├── approvePageToDomainHandler.go │ ├── bellUpdatesHandler.go │ ├── changeSpeedHandler.go │ ├── childrenJsonHandler.go │ ├── commentThreadHandler.go │ ├── continueWritingModeHandler.go │ ├── dashboardPageJsonHandler.go │ ├── defaultJsonHandler.go │ ├── deleteAnswerHandler.go │ ├── deleteLensHandler.go │ ├── deletePageHandler.go │ ├── deletePagePairHandler.go │ ├── deletePathPageHandler.go │ ├── deleteSearchStringHandler.go │ ├── discardPageHandler.go │ ├── discussionModeHandler.go │ ├── dismissUpdateHandler.go │ ├── domainPageJsonHandler.go │ ├── domainsHandler.go │ ├── dynamicPage.go │ ├── editJsonHandler.go │ ├── editPageHandler.go │ ├── editPageInfoHandler.go │ ├── exploreHandler.go │ ├── externalUrlHandler.go │ ├── feedPageHandler.go │ ├── feedbackHandler.go │ ├── forgotPasswordHandler.go │ ├── handler.go │ ├── hedonsModeHandler.go │ ├── indexJsonHandler.go │ ├── init.go │ ├── intrasitePopoverJsonHandler.go │ ├── learnJsonHandler.go │ ├── learnJsonHandler_test.go │ ├── lensJsonHandler.go │ ├── loginHandler.go │ ├── mailchimpSignupHandler.go │ ├── maintenanceModeHandler.go │ ├── marksJsonHandler.go │ ├── mergeQuestionsHandler.go │ ├── modeHelpers.go │ ├── monitor.go │ ├── moreRelationshipsJsonHandler.go │ ├── mostTodosJsonHandler.go │ ├── newAnswerHandler.go │ ├── newContentRequestHandler.go │ ├── newFeedPage.go │ ├── newInviteHandler.go │ ├── newLensHandler.go │ ├── newLikeHandler.go │ ├── newMarkHandler.go │ ├── newMemberHandler.go │ ├── newPageHandler.go │ ├── newPagePairHandler.go │ ├── newPageToDomainSubmissionHandler.go │ ├── newPathPageHandler.go │ ├── newSearchStringHandler.go │ ├── newVoteHandler.go │ ├── newsletterJsonHandler.go │ ├── pageHandler.go │ ├── pagePage.go │ ├── pagesWithDraftJsonHandler.go │ ├── parentsJsonHandler.go │ ├── parentsSearchJsonHandler.go │ ├── pendingModeHandler.go │ ├── primaryPageJsonHandler.go │ ├── projectHandler.go │ ├── projectsHandler.go │ ├── queue.yaml │ ├── readModeHandler.go │ ├── recentChangesHandler.go │ ├── recentRelationshipChanges.go │ ├── recentlyCreatedCommentJsonHandler.go │ ├── recentlyEditedJsonHandler.go │ ├── redLinkPopoverHandler.go │ ├── requisitesJsonHandler.go │ ├── resolveMarkHandler.go │ ├── resolveThreadHandler.go │ ├── revertPageHandler.go │ ├── searchJsonHandler.go │ ├── sendSlackInvite.go │ ├── sendTestEmailHandler.go │ ├── settingsPageJsonHandler.go │ ├── signupHandler.go │ ├── similarPageSearchJsonHanlder.go │ ├── startPathHandler.go │ ├── static │ │ ├── html │ │ │ ├── adminDashboardPage.html │ │ │ ├── answers.html │ │ │ ├── autocomplete.html │ │ │ ├── bellUpdatesPage.html │ │ │ ├── changeLogEntry.html │ │ │ ├── changeSpeedButton.html │ │ │ ├── checkbox.html │ │ │ ├── childRelationships.html │ │ │ ├── commentCount.html │ │ │ ├── composeFab.html │ │ │ ├── confirmButton.html │ │ │ ├── dashboardPage.html │ │ │ ├── debatePage.html │ │ │ ├── discussionModePage.html │ │ │ ├── domainCheckup.html │ │ │ ├── domainIndexPage.html │ │ │ ├── domainRoleInput.html │ │ │ ├── domainsPage.html │ │ │ ├── editButton.html │ │ │ ├── editClaimDialog.html │ │ │ ├── editDiff.html │ │ │ ├── editPage.html │ │ │ ├── editPageDialog.html │ │ │ ├── expandIcon.html │ │ │ ├── explorePage.html │ │ │ ├── exploreTreeNode.html │ │ │ ├── feedPage.html │ │ │ ├── feedbackDialog.html │ │ │ ├── footer.html │ │ │ ├── hedonsModePage.html │ │ │ ├── hubPageFooter.html │ │ │ ├── hubPageGui.html │ │ │ ├── indexPage.html │ │ │ ├── inlineComment.html │ │ │ ├── intrasitePopover.html │ │ │ ├── learnMore.html │ │ │ ├── learnPage.html │ │ │ ├── learnPart.html │ │ │ ├── lens.html │ │ │ ├── lensToolbar.html │ │ │ ├── lensToolbarWrapper.html │ │ │ ├── likes.html │ │ │ ├── listPanel.html │ │ │ ├── listSubHeader.html │ │ │ ├── loginPage.html │ │ │ ├── maintenanceModePage.html │ │ │ ├── markInfo.html │ │ │ ├── marks.html │ │ │ ├── masteryList.html │ │ │ ├── multipleChoice.html │ │ │ ├── newsletterPage.html │ │ │ ├── nextPrev.html │ │ │ ├── page.html │ │ │ ├── pageDiscussion.html │ │ │ ├── pageImprovement.html │ │ │ ├── pageList.html │ │ │ ├── pageTitle.html │ │ │ ├── paragraphEditDialog.html │ │ │ ├── pathEditor.html │ │ │ ├── pathNav.html │ │ │ ├── pendingPanel.html │ │ │ ├── primaryPage.html │ │ │ ├── projectPage.html │ │ │ ├── queryInfo.html │ │ │ ├── quickRequisiteDialog.html │ │ │ ├── readModePage.html │ │ │ ├── recentChangesPage.html │ │ │ ├── redLinkPopover.html │ │ │ ├── relationships.html │ │ │ ├── reqRelationships.html │ │ │ ├── requisiteButton.html │ │ │ ├── requisitesPage.html │ │ │ ├── rhsButtons.html │ │ │ ├── rows │ │ │ │ ├── addedToGroupModeRow.html │ │ │ │ ├── changeLogRow.html │ │ │ │ ├── commentModeRow.html │ │ │ │ ├── commentRowInternal.html │ │ │ │ ├── draftRow.html │ │ │ │ ├── editProposalRow.html │ │ │ │ ├── explanationRequestRow.html │ │ │ │ ├── inviteReceivedModeRow.html │ │ │ │ ├── likesModeRow.html │ │ │ │ ├── pageRow.html │ │ │ │ ├── pageToDomainSubmissionRow.html │ │ │ │ ├── removedFromGroupModeRow.html │ │ │ │ ├── reqsTaughtModeRow.html │ │ │ │ ├── taggedForEditRow.html │ │ │ │ ├── updates │ │ │ │ │ ├── atMentionUpdateRow.html │ │ │ │ │ ├── changeLogRowLikeButton.html │ │ │ │ │ ├── commentUpdateRow.html │ │ │ │ │ ├── deletedPageUpdateRow.html │ │ │ │ │ ├── editProposalAcceptedUpdateRow.html │ │ │ │ │ ├── markUpdateRow.html │ │ │ │ │ ├── pageEditUpdateRow.html │ │ │ │ │ ├── pageToDomainAcceptedUpdateRow.html │ │ │ │ │ ├── pageToDomainSubmissionUpdateRow.html │ │ │ │ │ ├── questionMergedUpdateRow.html │ │ │ │ │ ├── relationshipUpdateRow.html │ │ │ │ │ ├── resolvedThreadUpdateRow.html │ │ │ │ │ ├── settingsUpdateRow.html │ │ │ │ │ ├── updateRowDismissButton.html │ │ │ │ │ └── updateRowExpandButton.html │ │ │ │ └── userTrustModeRow.html │ │ │ ├── settingsPage.html │ │ │ ├── signupPage.html │ │ │ ├── slackButton.html │ │ │ ├── subpage.html │ │ │ ├── subscribeToDiscussion.html │ │ │ ├── subscribeToMaintain.html │ │ │ ├── subscribeToUser.html │ │ │ ├── summaryEditDialog.html │ │ │ ├── tableOfContents.html │ │ │ ├── textPopover.html │ │ │ ├── toolbar.html │ │ │ ├── userName.html │ │ │ ├── userPage.html │ │ │ ├── userPopover.html │ │ │ ├── voteBar.html │ │ │ ├── voteSummary.html │ │ │ └── writeNewPanel.html │ │ ├── icons │ │ │ ├── arbital-logo.svg │ │ │ ├── comment-plus-outline.svg │ │ │ ├── comment-question-outline.svg │ │ │ ├── cursor-pointer.svg │ │ │ ├── facebook-box.svg │ │ │ ├── favicon.ico │ │ │ ├── file-outline.svg │ │ │ ├── format-header-pound.svg │ │ │ ├── hand-pointing-right.svg │ │ │ ├── link-variant.svg │ │ │ ├── slack.svg │ │ │ ├── thumb-down-outline.svg │ │ │ ├── thumb-up-outline.svg │ │ │ └── visibility-outline.svg │ │ ├── images │ │ │ ├── arbital-icon-120.png │ │ │ ├── default-image-link.png │ │ │ ├── ic_border_color_black_24dp_1x.png │ │ │ └── math.png │ │ ├── js │ │ │ ├── .jscsrc │ │ │ ├── Markdown.Converter.ts │ │ │ ├── Markdown.Editor.ts │ │ │ ├── Markdown.Sanitizer.ts │ │ │ ├── adminDashboardPage.ts │ │ │ ├── analyticsService.ts │ │ │ ├── angular.ts │ │ │ ├── answers.ts │ │ │ ├── arbDirectives.ts │ │ │ ├── arbService.ts │ │ │ ├── arbital.d.ts │ │ │ ├── arbitalController.ts │ │ │ ├── autocompleteService.ts │ │ │ ├── changeElementType.ts │ │ │ ├── changeSpeedButton.ts │ │ │ ├── checkbox.ts │ │ │ ├── childRelationships.ts │ │ │ ├── dashboardPage.ts │ │ │ ├── debatePage.ts │ │ │ ├── diffService.ts │ │ │ ├── discussionMode.ts │ │ │ ├── domainCheckupPage.ts │ │ │ ├── domainIndexPage.ts │ │ │ ├── domainsPage.ts │ │ │ ├── editClaimDialog.ts │ │ │ ├── editDiff.ts │ │ │ ├── editPage.ts │ │ │ ├── editPageDialog.ts │ │ │ ├── editService.ts │ │ │ ├── explorePage.ts │ │ │ ├── exploreTreeNode.ts │ │ │ ├── feedPage.ts │ │ │ ├── feedbackDialog.ts │ │ │ ├── hedonsMode.ts │ │ │ ├── hiddenText.ts │ │ │ ├── hubPageFooter.ts │ │ │ ├── hubPageGui.ts │ │ │ ├── indexPage.ts │ │ │ ├── inlineCommentUtil.ts │ │ │ ├── learnMore.ts │ │ │ ├── learnPage.ts │ │ │ ├── lens.ts │ │ │ ├── lib │ │ │ │ ├── angular-recursion.min.js │ │ │ │ ├── angular-swipe.js │ │ │ │ ├── demo-bundle.js │ │ │ │ ├── js.cookie.js │ │ │ │ ├── moment.min.js │ │ │ │ └── ng-sortable.min.js │ │ │ ├── loginPage.ts │ │ │ ├── markInfo.ts │ │ │ ├── markService.ts │ │ │ ├── markdown.ts │ │ │ ├── markdownService.ts │ │ │ ├── marks.ts │ │ │ ├── masteryList.ts │ │ │ ├── masteryService.ts │ │ │ ├── mathjax.ts │ │ │ ├── multipleChoice.ts │ │ │ ├── newsletterPage.ts │ │ │ ├── page.ts │ │ │ ├── pageDiscussion.ts │ │ │ ├── pageImprovement.ts │ │ │ ├── pageService.ts │ │ │ ├── paragraphEditDialog.ts │ │ │ ├── pathEditor.ts │ │ │ ├── pathNav.ts │ │ │ ├── pathService.ts │ │ │ ├── popoverService.ts │ │ │ ├── popupService.ts │ │ │ ├── primaryPage.ts │ │ │ ├── projectPage.ts │ │ │ ├── queryInfo.ts │ │ │ ├── quickRequisiteDialogController.ts │ │ │ ├── readMode.ts │ │ │ ├── recentChanges.ts │ │ │ ├── relationships.ts │ │ │ ├── reqRelationships.ts │ │ │ ├── requisitesPage.ts │ │ │ ├── rhsButtons.ts │ │ │ ├── settingsPage.ts │ │ │ ├── signupPage.ts │ │ │ ├── signupService.ts │ │ │ ├── stateService.ts │ │ │ ├── subpage.ts │ │ │ ├── summaryEditDialogController.ts │ │ │ ├── tableOfContents.ts │ │ │ ├── toolbar.ts │ │ │ ├── tsconfig.json │ │ │ ├── typings.json │ │ │ ├── untitled │ │ │ ├── updateRows.ts │ │ │ ├── updatesMode.ts │ │ │ ├── urlService.ts │ │ │ ├── userPage.ts │ │ │ ├── userService.ts │ │ │ ├── util.ts │ │ │ ├── voteBar.ts │ │ │ └── writeMode.ts │ │ ├── scss │ │ │ ├── arbital.scss │ │ │ ├── buttons.scss │ │ │ ├── changeSpeedButton.scss │ │ │ ├── constants.scss │ │ │ ├── ng-material.scss │ │ │ ├── ng-sortable.scss │ │ │ └── util.scss │ │ ├── updatesEmailInlined.html │ │ └── updatesEmailTemplate.html │ ├── titleJsonHandler.go │ ├── tmpl │ │ └── dynamicPage.tmpl │ ├── tsx │ ├── unassessedPagesHandler.go │ ├── updateDomainHandler.go │ ├── updateDomainRoleHandler.go │ ├── updateLensNameHandler.go │ ├── updateLensOrderHandler.go │ ├── updateMarkHandler.go │ ├── updateMasteriesHandler.go │ ├── updateMasteriesOldHandler.go │ ├── updateMemberHandler.go │ ├── updatePageObjectHandler.go │ ├── updatePagePairHandler.go │ ├── updatePathHandler.go │ ├── updatePathOrderHandler.go │ ├── updateSettingsHandler.go │ ├── updateSubscriptionHandler.go │ ├── userPopoverJsonHandler.go │ ├── userSearchJsonHandler.go │ ├── verifyEmailPage.go │ ├── webpack │ │ ├── base.config.js │ │ ├── dev.config.js │ │ ├── entry.js │ │ └── prod.config.js │ └── writeNewModeHandler.go ├── tasks │ ├── atMentionUpdateTask.go │ ├── checkAnsweredMarksTask.go │ ├── copyPagesTask.go │ ├── domainWideNewUpdateTask.go │ ├── emailUpdatesTask.go │ ├── fixTextTask.go │ ├── init.go │ ├── memberUpdateTask.go │ ├── newUpdateTask.go │ ├── populateElasticTask.go │ ├── publishPagePairTask.go │ ├── sendFeedbackEmailTask.go │ ├── sendInviteTask.go │ ├── sendOneEmailTask.go │ ├── task.go │ ├── tickTask.go │ ├── updateElasticPageTask.go │ ├── updateFeaturedPagesTask.go │ ├── updateMetadataTask.go │ └── updatePagePairsTask.go └── v2 │ ├── app.yaml │ ├── dynamicPage.go │ ├── handler.go │ ├── init.go │ ├── pageHandler.go │ ├── static │ └── tsx │ │ └── script.tsx │ ├── tmpl │ └── dynamicPage.tmpl │ └── webpack │ ├── base.config.js │ ├── dev.config.js │ ├── entry.js │ └── prod.config.js ├── test ├── e2e │ ├── README.md │ └── scenarios.js ├── karma.conf.js ├── protractor-conf.js └── unit │ ├── controllersSpec.js │ ├── directivesSpec.js │ ├── filtersSpec.js │ └── servicesSpec.js └── vendor ├── github.com ├── dustin │ └── go-humanize │ │ ├── LICENSE │ │ ├── README.markdown │ │ ├── big.go │ │ ├── bigbytes.go │ │ ├── bytes.go │ │ ├── comma.go │ │ ├── commaf.go │ │ ├── ftoa.go │ │ ├── humanize.go │ │ ├── number.go │ │ ├── ordinals.go │ │ ├── si.go │ │ └── times.go ├── dyatlov │ └── go-opengraph │ │ ├── LICENSE │ │ ├── README.md │ │ ├── examples │ │ ├── advanced.go │ │ └── simple.go │ │ └── opengraph │ │ ├── opengraph.go │ │ └── opengraph_test.go ├── garyburd │ └── go-oauth │ │ └── oauth │ │ └── oauth.go ├── go-sql-driver │ └── mysql │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── CONTRIBUTING.md │ │ ├── ISSUE_TEMPLATE.md │ │ ├── LICENSE │ │ ├── PULL_REQUEST_TEMPLATE.md │ │ ├── README.md │ │ ├── appengine.go │ │ ├── buffer.go │ │ ├── collations.go │ │ ├── connection.go │ │ ├── const.go │ │ ├── driver.go │ │ ├── dsn.go │ │ ├── errors.go │ │ ├── infile.go │ │ ├── packets.go │ │ ├── result.go │ │ ├── rows.go │ │ ├── statement.go │ │ ├── transaction.go │ │ └── utils.go ├── golang │ ├── glog │ │ ├── LICENSE │ │ ├── README │ │ ├── glog.go │ │ └── glog_file.go │ └── protobuf │ │ ├── LICENSE │ │ └── proto │ │ ├── Makefile │ │ ├── clone.go │ │ ├── decode.go │ │ ├── encode.go │ │ ├── equal.go │ │ ├── extensions.go │ │ ├── lib.go │ │ ├── message_set.go │ │ ├── pointer_reflect.go │ │ ├── pointer_unsafe.go │ │ ├── properties.go │ │ ├── text.go │ │ └── text_parser.go ├── gorilla │ ├── context │ │ ├── LICENSE │ │ ├── README.md │ │ ├── context.go │ │ └── doc.go │ ├── mux │ │ ├── LICENSE │ │ ├── README.md │ │ ├── context_gorilla.go │ │ ├── context_native.go │ │ ├── doc.go │ │ ├── mux.go │ │ ├── regexp.go │ │ └── route.go │ ├── securecookie │ │ ├── LICENSE │ │ ├── README.md │ │ ├── doc.go │ │ ├── fuzz.go │ │ └── securecookie.go │ └── sessions │ │ ├── LICENSE │ │ ├── README.md │ │ ├── doc.go │ │ ├── lex.go │ │ ├── sessions.go │ │ └── store.go └── imdario │ └── mergo │ ├── LICENSE │ ├── README.md │ ├── doc.go │ ├── map.go │ ├── merge.go │ └── mergo.go ├── golang.org └── x │ └── net │ ├── LICENSE │ ├── PATENTS │ ├── context │ ├── context.go │ ├── go17.go │ └── pre_go17.go │ └── html │ ├── atom │ ├── atom.go │ ├── atom_test.go │ ├── gen.go │ ├── table.go │ └── table_test.go │ ├── charset │ ├── charset.go │ ├── charset_test.go │ └── testdata │ │ ├── HTTP-charset.html │ │ ├── HTTP-vs-UTF-8-BOM.html │ │ ├── HTTP-vs-meta-charset.html │ │ ├── HTTP-vs-meta-content.html │ │ ├── No-encoding-declaration.html │ │ ├── README │ │ ├── UTF-16BE-BOM.html │ │ ├── UTF-16LE-BOM.html │ │ ├── UTF-8-BOM-vs-meta-charset.html │ │ ├── UTF-8-BOM-vs-meta-content.html │ │ ├── meta-charset-attribute.html │ │ └── meta-content-attribute.html │ ├── const.go │ ├── doc.go │ ├── doctype.go │ ├── entity.go │ ├── entity_test.go │ ├── escape.go │ ├── escape_test.go │ ├── example_test.go │ ├── foreign.go │ ├── node.go │ ├── node_test.go │ ├── parse.go │ ├── parse_test.go │ ├── render.go │ ├── render_test.go │ ├── testdata │ ├── go1.html │ └── webkit │ │ ├── README │ │ ├── adoption01.dat │ │ ├── adoption02.dat │ │ ├── comments01.dat │ │ ├── doctype01.dat │ │ ├── entities01.dat │ │ ├── entities02.dat │ │ ├── html5test-com.dat │ │ ├── inbody01.dat │ │ ├── isindex.dat │ │ ├── pending-spec-changes-plain-text-unsafe.dat │ │ ├── pending-spec-changes.dat │ │ ├── plain-text-unsafe.dat │ │ ├── scriptdata01.dat │ │ ├── scripted │ │ ├── adoption01.dat │ │ └── webkit01.dat │ │ ├── tables01.dat │ │ ├── tests1.dat │ │ ├── tests10.dat │ │ ├── tests11.dat │ │ ├── tests12.dat │ │ ├── tests14.dat │ │ ├── tests15.dat │ │ ├── tests16.dat │ │ ├── tests17.dat │ │ ├── tests18.dat │ │ ├── tests19.dat │ │ ├── tests2.dat │ │ ├── tests20.dat │ │ ├── tests21.dat │ │ ├── tests22.dat │ │ ├── tests23.dat │ │ ├── tests24.dat │ │ ├── tests25.dat │ │ ├── tests26.dat │ │ ├── tests3.dat │ │ ├── tests4.dat │ │ ├── tests5.dat │ │ ├── tests6.dat │ │ ├── tests7.dat │ │ ├── tests8.dat │ │ ├── tests9.dat │ │ ├── tests_innerHTML_1.dat │ │ ├── tricky01.dat │ │ ├── webkit01.dat │ │ └── webkit02.dat │ ├── token.go │ └── token_test.go ├── google.golang.org └── appengine │ ├── LICENSE │ ├── README.md │ ├── appengine.go │ ├── appengine_vm.go │ ├── cloudsql │ ├── cloudsql.go │ ├── cloudsql_classic.go │ └── cloudsql_vm.go │ ├── errors.go │ ├── identity.go │ ├── internal │ ├── api.go │ ├── api_classic.go │ ├── api_common.go │ ├── app_id.go │ ├── app_identity │ │ ├── app_identity_service.pb.go │ │ └── app_identity_service.proto │ ├── base │ │ ├── api_base.pb.go │ │ └── api_base.proto │ ├── datastore │ │ ├── datastore_v3.pb.go │ │ └── datastore_v3.proto │ ├── identity.go │ ├── identity_classic.go │ ├── identity_vm.go │ ├── internal.go │ ├── log │ │ ├── log_service.pb.go │ │ └── log_service.proto │ ├── mail │ │ ├── mail_service.pb.go │ │ └── mail_service.proto │ ├── metadata.go │ ├── modules │ │ ├── modules_service.pb.go │ │ └── modules_service.proto │ ├── net.go │ ├── regen.sh │ ├── remote_api │ │ ├── remote_api.pb.go │ │ └── remote_api.proto │ ├── socket │ │ ├── socket_service.pb.go │ │ └── socket_service.proto │ ├── taskqueue │ │ ├── taskqueue_service.pb.go │ │ └── taskqueue_service.proto │ ├── transaction.go │ └── urlfetch │ │ ├── urlfetch_service.pb.go │ │ └── urlfetch_service.proto │ ├── log │ ├── api.go │ └── log.go │ ├── mail │ └── mail.go │ ├── namespace.go │ ├── socket │ ├── doc.go │ ├── socket_classic.go │ └── socket_vm.go │ ├── taskqueue │ └── taskqueue.go │ ├── timeout.go │ └── urlfetch │ └── urlfetch.go ├── gopkg.in └── yaml.v2 │ ├── LICENSE │ ├── LICENSE.libyaml │ ├── README.md │ ├── apic.go │ ├── decode.go │ ├── emitterc.go │ ├── encode.go │ ├── parserc.go │ ├── readerc.go │ ├── resolve.go │ ├── scannerc.go │ ├── sorter.go │ ├── writerc.go │ ├── yaml.go │ ├── yamlh.go │ └── yamlprivateh.go └── vendor.json /.gitignore: -------------------------------------------------------------------------------- 1 | \*scratch* 2 | [#]*[#] 3 | .#* 4 | *~ 5 | .gcloud/* 6 | */*/config.yaml 7 | *.pyc 8 | *.log* 9 | .DS_Store 10 | /prober 11 | /xelaiemon 12 | *.cp 13 | *.swp 14 | *.css.map 15 | .sass-cache 16 | /bower_components 17 | /node_modules 18 | /.sass-cache 19 | 20 | # (Alexei) I created some sym-links from site directory to static directory for myself 21 | src/site/html 22 | src/site/js 23 | src/site/scss 24 | db1 25 | 26 | bundle.js 27 | src/site/static/js/typings/ 28 | 29 | .last_deps_update 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machine-intelligence/arbital-open-source/ce5ee1c472e39834fbd61b895ba23b501aa96e54/README.md -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arbital", 3 | "version": "0.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "jquery": "~2.2.0", 7 | "angular": "~1.4.8", 8 | "angular-mocks": "~1.4.8", 9 | "angular-route": "~1.4.8", 10 | "MathJax": "~2.6.0", 11 | "angular-animate": "~1.4.8", 12 | "angular-aria": "~1.4.8", 13 | "angular-material": "~1.0.2", 14 | "angular-messages": "~1.4.8", 15 | "angular-recursion": "~1.0.5", 16 | "angular-resource": "~1.4.8", 17 | "angular-sanitize": "~1.4.8" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "private": true, 4 | "name": "arbital", 5 | "devDependencies": { 6 | "fsevents": "^1.0.17" 7 | }, 8 | "scripts": { 9 | "postinstall": "cd src/site/static/js && typings install", 10 | "typings": "cd src/site/static/js && typings", 11 | "webpack": "cd src/site/webpack && webpack", 12 | "webpack-dev-server": "cd src/site/webpack && webpack-dev-server", 13 | "prestart": "npm install", 14 | "start": "scripts/stage.sh" 15 | }, 16 | "dependencies": { 17 | "css-loader": "^0.23.1", 18 | "deepmerge": "^0.2.10", 19 | "diff-match-patch": "^1.0.0", 20 | "html-loader": "^0.4.3", 21 | "loader-utils": "^0.2.15", 22 | "ngtemplate-loader": "^1.3.1", 23 | "node-sass": "^3.8.0", 24 | "sass-loader": "^4.0.0", 25 | "source-map-loader": "^0.1.5", 26 | "style-loader": "^0.13.1", 27 | "ts-loader": "^0.8.2", 28 | "typescript": "^1.8.10", 29 | "typings": "^1.3.2", 30 | "webpack": "^1.13.1", 31 | "webpack-dev-server": "^1.14.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /schemas/aliasRedirects.sql: -------------------------------------------------------------------------------- 1 | /* When a page's alias is changed, we add a row in this table. */ 2 | CREATE TABLE aliasRedirects ( 3 | 4 | /* The old alias. */ 5 | oldAlias VARCHAR(64) NOT NULL, 6 | 7 | /* The new alias. */ 8 | newAlias VARCHAR(64) NOT NULL, 9 | 10 | UNIQUE(oldAlias) 11 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 12 | -------------------------------------------------------------------------------- /schemas/anchors.sql: -------------------------------------------------------------------------------- 1 | /* TODO: we are not actually using this table ATM, but probably should. */ 2 | /* This table contains all the anchors. An anchor determines a specific place 3 | inside a page, including the paragraph and the specific text within it. */ 4 | CREATE TABLE anchors ( 5 | /* Id of this anchor. */ 6 | id BIGINT NOT NULL AUTO_INCREMENT, 7 | /* Text of the paragraph the anchor is in. */ 8 | paragraph MEDIUMTEXT NOT NULL, 9 | /* Text within the paragraph. If empty, assume it's attached to the entire 10 | pararaph. */ 11 | text MEDIUMTEXT NOT NULL, 12 | /* Offset of the text inside the paragraph. */ 13 | offset INT NOT NULL, 14 | /* When this was created. */ 15 | createdAt DATETIME NOT NULL, 16 | 17 | PRIMARY KEY(id) 18 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 19 | -------------------------------------------------------------------------------- /schemas/answers.sql: -------------------------------------------------------------------------------- 1 | /* A row for every answer. An answer is a pointer to another page, and it's always 2 | attached to a question. */ 3 | CREATE TABLE answers ( 4 | /* Id of this answer. */ 5 | id BIGINT NOT NULL AUTO_INCREMENT, 6 | /* Id of the question this answer is for. FK into pages. */ 7 | questionId VARCHAR(32) NOT NULL, 8 | /* Id of the user who added this string. FK into users. */ 9 | answerPageId VARCHAR(32) NOT NULL, 10 | /* Id of the user who added this answer. FK into users. */ 11 | userId VARCHAR(32) NOT NULL, 12 | /* Date this answer was created. */ 13 | createdAt DATETIME NOT NULL, 14 | PRIMARY KEY(id) 15 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 16 | -------------------------------------------------------------------------------- /schemas/changeLogs.sql: -------------------------------------------------------------------------------- 1 | /* This table contains an entry for every change that a page undergoes. */ 2 | CREATE TABLE changeLogs ( 3 | 4 | /* Unique update id. PK. */ 5 | id BIGINT NOT NULL AUTO_INCREMENT, 6 | 7 | /* Likeable id for this changelog. Partial FK into likes. 8 | Note that this is not set until the first time this changelog is liked. */ 9 | likeableId BIGINT NOT NULL, 10 | 11 | /* The user who caused this event. FK into users. */ 12 | userId VARCHAR(32) NOT NULL, 13 | 14 | /* The affected page. FK into pages. */ 15 | pageId VARCHAR(32) NOT NULL, 16 | 17 | /* Optional edit number of the affected page. Partial FK into pages. */ 18 | edit INT NOT NULL, 19 | 20 | /* Type of update */ 21 | type VARCHAR(32) NOT NULL, 22 | 23 | /* When this update was created. */ 24 | createdAt DATETIME NOT NULL, 25 | 26 | /* This is set for various events. E.g. if a new parent is added, this will 27 | be set to the parent id. */ 28 | auxPageId VARCHAR(32) NOT NULL, 29 | 30 | /* So that we can show what changed in the change log. */ 31 | oldSettingsValue VARCHAR(1024) NOT NULL, 32 | newSettingsValue VARCHAR(1024) NOT NULL, 33 | 34 | PRIMARY KEY(id) 35 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 36 | -------------------------------------------------------------------------------- /schemas/contentRequests.sql: -------------------------------------------------------------------------------- 1 | /* An entry for every content request pair (page and type) */ 2 | CREATE TABLE contentRequests ( 3 | 4 | /* Id of the request. */ 5 | id BIGINT NOT NULL AUTO_INCREMENT, 6 | 7 | /* The page the request was made for. FK into pages. */ 8 | pageId VARCHAR(32) NOT NULL, 9 | 10 | /* Type of request. E.g. slowDown, speedUp, etc. */ 11 | type VARCHAR(32) NOT NULL, 12 | 13 | /* Id by which we track likes. FK into likes. */ 14 | likeableId BIGINT NOT NULL, 15 | 16 | /* Date this entry was created. */ 17 | createdAt DATETIME NOT NULL, 18 | 19 | /* There can only be one row per (page, type) pair */ 20 | UNIQUE KEY(pageId,type), 21 | 22 | PRIMARY KEY(id) 23 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 24 | -------------------------------------------------------------------------------- /schemas/discussionSubscriptions.sql: -------------------------------------------------------------------------------- 1 | /* This table contains all the subscriptions to discussions (page or comments). */ 2 | CREATE TABLE discussionSubscriptions ( 3 | 4 | /* User id of the subscriber. FK into users. */ 5 | userId VARCHAR(32) NOT NULL, 6 | 7 | /* Id of page/comment the user is subscribed to. FK into pageInfos. */ 8 | toPageId VARCHAR(32) NOT NULL, 9 | 10 | /* When this subscription was created. */ 11 | createdAt DATETIME NOT NULL, 12 | 13 | PRIMARY KEY(userId, toPageId) 14 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 15 | -------------------------------------------------------------------------------- /schemas/domainFriends.sql: -------------------------------------------------------------------------------- 1 | /* This table contains all domain pairs that are friendly with each other. */ 2 | CREATE TABLE domainFriends ( 3 | /* Domain id. FK into domains. */ 4 | domainId BIGINT NOT NULL, 5 | /* Id of another domain this domain is friends with. FK into domains. */ 6 | friendId BIGINT NOT NULL, 7 | /* When this friendship was originally created. */ 8 | createdAt DATETIME NOT NULL, 9 | /* Id of the user who created the friendship. FK into users. */ 10 | createdBy VARCHAR(32) NOT NULL, 11 | 12 | UNIQUE(domainId,friendId) 13 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 14 | -------------------------------------------------------------------------------- /schemas/domainMembers.sql: -------------------------------------------------------------------------------- 1 | /* An entry for every member in a domain. */ 2 | CREATE TABLE domainMembers ( 3 | 4 | /* Id of the domain. FK into domains. */ 5 | domainId BIGINT NOT NULL, 6 | 7 | /* Id of the user member. FK into users. */ 8 | userId VARCHAR(32) NOT NULL, 9 | 10 | /* Date this user was added. */ 11 | createdAt DATETIME NOT NULL, 12 | 13 | /* User's role in this domain. */ 14 | role VARCHAR(32) NOT NULL, 15 | 16 | PRIMARY KEY(domainId,userId) 17 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 18 | -------------------------------------------------------------------------------- /schemas/domains.sql: -------------------------------------------------------------------------------- 1 | /* This table contains all domains and relevant info. */ 2 | CREATE TABLE domains ( 3 | /* Domain id. */ 4 | id BIGINT NOT NULL AUTO_INCREMENT, 5 | /* Id of the home page for this domain. FK into pageInfos. */ 6 | pageId VARCHAR(32) NOT NULL, 7 | /* When this page was originally created. */ 8 | createdAt DATETIME NOT NULL, 9 | /* Id of the user who created the page. FK into users. */ 10 | createdBy VARCHAR(32) NOT NULL, 11 | /* Alias name of the domain. */ 12 | alias VARCHAR(64) NOT NULL, 13 | 14 | /* ============ Various domain settings ============ */ 15 | /* If true, any registered user can comment. */ 16 | canUsersComment BOOL NOT NULL, 17 | /* If true, any registered user can propose a comment. */ 18 | canUsersProposeComment BOOL NOT NULL, 19 | /* If true, any registered user can propose an edit. */ 20 | canUsersProposeEdits BOOL NOT NULL, 21 | 22 | PRIMARY KEY(id) 23 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 24 | -------------------------------------------------------------------------------- /schemas/feedPages.sql: -------------------------------------------------------------------------------- 1 | /* This table contains all the feed pages. */ 2 | CREATE TABLE feedPages ( 3 | 4 | /* Id of the domain feed. FK into domains. */ 5 | domainId BIGINT NOT NULL, 6 | 7 | /* Id of the page in the feed. FK into pageInfos. */ 8 | pageId VARCHAR(32) NOT NULL, 9 | 10 | /* Id of the user who submitted it to the feed. FK into users. */ 11 | submitterId VARCHAR(32) NOT NULL, 12 | 13 | /* When this submission was made. */ 14 | createdAt DATETIME NOT NULL, 15 | 16 | /* Score for this feed page, determining where it appears in the feed. */ 17 | score DOUBLE NOT NULL, 18 | 19 | PRIMARY KEY(domainId, pageId) 20 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 21 | -------------------------------------------------------------------------------- /schemas/invites.sql: -------------------------------------------------------------------------------- 1 | /* An entry for every invite. */ 2 | CREATE TABLE invites ( 3 | /* Id of user sending invite. FK into users. */ 4 | fromUserId VARCHAR(32) NOT NULL, 5 | /* Id of domain that this invite is for. FK into domains. */ 6 | domainId BIGINT NOT NULL, 7 | /* Role the invited user will receive. */ 8 | role VARCHAR(32) NOT NULL, 9 | /* Email address to send invite to. */ 10 | toEmail VARCHAR(100) NOT NULL, 11 | /* Date this invite was created. */ 12 | createdAt DATETIME NOT NULL, 13 | /* If a user claimed this invite, this is their id. FK into users. */ 14 | toUserId VARCHAR(32) NOT NULL, 15 | /* Date this invite was claimed */ 16 | claimedAt DATETIME NOT NULL, 17 | /* When the invite email was sent. */ 18 | emailSentAt DATETIME NOT NULL, 19 | 20 | PRIMARY KEY(fromUserId,domainId,toEmail) 21 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 22 | -------------------------------------------------------------------------------- /schemas/lastViews.sql: -------------------------------------------------------------------------------- 1 | /* A table for keeping track of the last time the user saw various things */ 2 | CREATE TABLE lastViews ( 3 | /* Id of the user who viewed the thing. */ 4 | userId varchar(32) NOT NULL, 5 | /* The thing the user viewed. */ 6 | viewName varchar(64) NOT NULL, 7 | /* The last time the user viewed the thing. */ 8 | viewedAt DATETIME NOT NULL, 9 | 10 | PRIMARY KEY(userId,viewName) 11 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 12 | -------------------------------------------------------------------------------- /schemas/lastVisits.sql: -------------------------------------------------------------------------------- 1 | /* Each row is a page-user pair with the date and time when the user has last seen the page. */ 2 | CREATE TABLE lastVisits ( 3 | 4 | /* FK into users. */ 5 | userId VARCHAR(64) NOT NULL, 6 | 7 | /* Page id. FK into pages. */ 8 | pageId VARCHAR(32) NOT NULL, 9 | 10 | /* Date of the first visit. */ 11 | createdAt DATETIME NOT NULL, 12 | 13 | /* Date of the last visit. */ 14 | updatedAt DATETIME NOT NULL, 15 | 16 | UNIQUE(userId,pageId) 17 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 18 | -------------------------------------------------------------------------------- /schemas/lenses.sql: -------------------------------------------------------------------------------- 1 | /* This table contains all information about lens relationships. */ 2 | CREATE TABLE lenses ( 3 | /* Id of the lens relationships. */ 4 | id BIGINT NOT NULL AUTO_INCREMENT, 5 | /* Id of the page that has the lens. FK into pageInfos. */ 6 | pageId VARCHAR(32) NOT NULL, 7 | /* Id of the lens page. FK into pageInfos. */ 8 | lensId VARCHAR(32) NOT NULL, 9 | /* Ordering index when sorting the page's lenses. */ 10 | lensIndex INT NOT NULL, 11 | /* Lens name that shows up in the tab. */ 12 | lensName VARCHAR(32) NOT NULL, 13 | /* Lens subtitle that shows up in the tab. */ 14 | lensSubtitle VARCHAR(256) NOT NULL, 15 | /* Id of the user who created the relationship. FK into users. */ 16 | createdBy VARCHAR(32) NOT NULL, 17 | /* When this lens relationship was originally created. */ 18 | createdAt DATETIME NOT NULL, 19 | /* Id of the last user who updated the relationship. FK into users. */ 20 | updatedBy VARCHAR(32) NOT NULL, 21 | /* When this relationship was updated last. */ 22 | updatedAt DATETIME NOT NULL, 23 | 24 | UNIQUE(lensId), 25 | /* This constraint should apply, but makes it very difficult to update the lensIndex for multiple rows */ 26 | /*UNIQUE(pageId,lensIndex),*/ 27 | PRIMARY KEY(id) 28 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 29 | -------------------------------------------------------------------------------- /schemas/likeableIds.sql: -------------------------------------------------------------------------------- 1 | /* A table for keeping track of likeableIds */ 2 | CREATE TABLE likeableIds ( 3 | /* Id of the likeable. */ 4 | id BIGINT NOT NULL AUTO_INCREMENT, 5 | PRIMARY KEY(id) 6 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 7 | -------------------------------------------------------------------------------- /schemas/likes.sql: -------------------------------------------------------------------------------- 1 | /* An entry for every like a user cast for a likeable object, such as a page 2 | or changelog. */ 3 | CREATE TABLE likes ( 4 | /* Id of the user who liked. FK into users. */ 5 | userId VARCHAR(32) NOT NULL, 6 | 7 | /* Id of the likeable this like is for. */ 8 | likeableId BIGINT NOT NULL, 9 | 10 | /* User's trust when they made the like. FK into userTrustSnapshots */ 11 | userTrustSnapshotId BIGINT NOT NULL, 12 | 13 | /* Like value [-1,1]. */ 14 | value TINYINT NOT NULL, 15 | 16 | /* Date this like was created. */ 17 | createdAt DATETIME NOT NULL, 18 | 19 | /* Date this like was updated. */ 20 | updatedAt DATETIME NOT NULL, 21 | 22 | PRIMARY KEY(userId,likeableId) 23 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 24 | -------------------------------------------------------------------------------- /schemas/links.sql: -------------------------------------------------------------------------------- 1 | /* When a parent page has a link to a child page, we add a row in this table. */ 2 | CREATE TABLE links ( 3 | 4 | /* Id of the parent page. FK into pages. */ 5 | parentId VARCHAR(32) NOT NULL, 6 | 7 | /* Alias or id of the child claim. */ 8 | childAlias VARCHAR(64) NOT NULL, 9 | 10 | UNIQUE(parentId, childAlias) 11 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 12 | -------------------------------------------------------------------------------- /schemas/maintainerSubscriptions.sql: -------------------------------------------------------------------------------- 1 | /* This table contains all the maintainance subscriptions. */ 2 | CREATE TABLE maintainerSubscriptions ( 3 | 4 | /* User id of the subscriber. FK into users. */ 5 | userId VARCHAR(32) NOT NULL, 6 | 7 | /* Id of the page the user is subscribed to. FK into pageInfos. */ 8 | toPageId VARCHAR(32) NOT NULL, 9 | 10 | /* When this subscription was created. */ 11 | createdAt DATETIME NOT NULL, 12 | 13 | PRIMARY KEY(userId, toPageId) 14 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 15 | -------------------------------------------------------------------------------- /schemas/pageSummaries.sql: -------------------------------------------------------------------------------- 1 | /* This table contains all the summaries for all the pages. */ 2 | CREATE TABLE pageSummaries ( 3 | 4 | /* Id of the page the summary is for. */ 5 | pageId VARCHAR(32) NOT NULL, 6 | 7 | /* Name of the summary. */ 8 | name VARCHAR(32) NOT NULL, 9 | 10 | /* Text of the summary. */ 11 | text TEXT NOT NULL, 12 | 13 | PRIMARY KEY(pageId, name) 14 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 15 | 16 | -------------------------------------------------------------------------------- /schemas/pageToDomainSubmissions.sql: -------------------------------------------------------------------------------- 1 | /* This table contains pages that have been submitted to a domain. */ 2 | CREATE TABLE pageToDomainSubmissions ( 3 | /* Id of the submitted page. FK into pageInfos. */ 4 | pageId VARCHAR(32) NOT NULL, 5 | /* Id of the domain it's submitted to. FK into pageInfos. */ 6 | domainId VARCHAR(32) NOT NULL, 7 | /* When this submission was originally created. */ 8 | createdAt DATETIME NOT NULL, 9 | /* Id of the user who submitted. FK into users. */ 10 | submitterId VARCHAR(32) NOT NULL, 11 | 12 | /* When this submission was approved. */ 13 | approvedAt DATETIME NOT NULL, 14 | /* Id of the user who approved the submission. FK into users. */ 15 | approverId VARCHAR(32) NOT NULL, 16 | 17 | PRIMARY KEY(pageId,domainId) 18 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 19 | -------------------------------------------------------------------------------- /schemas/pathInstances.sql: -------------------------------------------------------------------------------- 1 | /* This table contains a row for each path a user has started. */ 2 | CREATE TABLE pathInstances ( 3 | /* Id of this entry. */ 4 | id BIGINT NOT NULL AUTO_INCREMENT, 5 | /* User who started this path. FK into users. */ 6 | userId VARCHAR(32) NOT NULL, 7 | /* Id of the page guide that started this path. FK into pageInfos. */ 8 | guideId VARCHAR(32) NOT NULL, 9 | /* Comma separated list of page ids which this path has. FK into pageInfos. */ 10 | pageIds TEXT NOT NULL, 11 | /* Comma separated list of which page added the corresponding page to pageIds. FK into pageInfos. */ 12 | sourcePageIds TEXT NOT NULL, 13 | /* Index of the page the user is on. */ 14 | progress INT NOT NULL, 15 | /* When this instance was created. */ 16 | createdAt DATETIME NOT NULL, 17 | /* When this instance was updated last. */ 18 | updatedAt DATETIME NOT NULL, 19 | /* Optional. If set, the user copied the path from this instance. */ 20 | originalInstanceId BIGINT NOT NULL, 21 | /* Set to true when the user finished the path. */ 22 | isFinished BOOLEAN NOT NULL, 23 | 24 | PRIMARY KEY(id) 25 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 26 | -------------------------------------------------------------------------------- /schemas/pathPages.sql: -------------------------------------------------------------------------------- 1 | /* This table contains what pages belong to which paths. */ 2 | CREATE TABLE pathPages ( 3 | /* Id of this entry. */ 4 | id BIGINT NOT NULL AUTO_INCREMENT, 5 | /* Id of the page guide that starts this path. FK into pageInfos. */ 6 | guideId VARCHAR(32) NOT NULL, 7 | /* Id of one of the pages on the path. FK into pageInfos. */ 8 | pathPageId VARCHAR(32) NOT NULL, 9 | /* Ordering index when ordering the pages in a path. */ 10 | pathIndex INT NOT NULL, 11 | /* Id of the user who created the relationship. FK into users. */ 12 | createdBy VARCHAR(32) NOT NULL, 13 | /* When this lens relationship was originally created. */ 14 | createdAt DATETIME NOT NULL, 15 | /* Id of the last user who updated the relationship. FK into users. */ 16 | updatedBy VARCHAR(32) NOT NULL, 17 | /* When this relationship was updated last. */ 18 | updatedAt DATETIME NOT NULL, 19 | 20 | /* This constraint should apply, but makes it very difficult to update the index for multiple rows */ 21 | /* UNIQUE(guideId,index),*/ 22 | PRIMARY KEY(id) 23 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 24 | -------------------------------------------------------------------------------- /schemas/projects.sql: -------------------------------------------------------------------------------- 1 | /* An entry for every project we have */ 2 | CREATE TABLE projects ( 3 | /* Project id. */ 4 | id BIGINT NOT NULL AUTO_INCREMENT, 5 | 6 | /* The page which describes this project. FK into pages. */ 7 | projectPageId VARCHAR(32) NOT NULL, 8 | 9 | /* The first page the reader should go to. FK into pages. */ 10 | startPageId VARCHAR(32) NOT NULL, 11 | 12 | /* State of the project. "finished", "inProgress", or "requested" */ 13 | state VARCHAR(32) NOT NULL, 14 | 15 | /* Id by which we track who wants to read this. FK into likes. */ 16 | readLikeableId BIGINT NOT NULL, 17 | 18 | /* Id by which we track who wants to write this. FK into likes. */ 19 | writeLikeableId BIGINT NOT NULL, 20 | 21 | /* Date this entry was created. */ 22 | createdAt DATETIME NOT NULL, 23 | 24 | PRIMARY KEY(id) 25 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 26 | -------------------------------------------------------------------------------- /schemas/redLinks.sql: -------------------------------------------------------------------------------- 1 | /* An entry for every red link. */ 2 | CREATE TABLE redLinks ( 3 | /* Alias of the red link. */ 4 | alias VARCHAR(64) NOT NULL, 5 | 6 | /* Id by which we track likes. Partial FK into likes. */ 7 | likeableId BIGINT NOT NULL, 8 | 9 | /* Date this entry was created. */ 10 | createdAt DATETIME NOT NULL, 11 | 12 | PRIMARY KEY(alias) 13 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 14 | -------------------------------------------------------------------------------- /schemas/searchStrings.sql: -------------------------------------------------------------------------------- 1 | /* An entry for every search string that's attached to a page. */ 2 | CREATE TABLE searchStrings ( 3 | /* Id of this search string. */ 4 | id BIGINT NOT NULL AUTO_INCREMENT, 5 | /* Id of the page this string is for. FK into pages. */ 6 | pageId VARCHAR(32) NOT NULL, 7 | /* Id of the user who added this string. FK into users. */ 8 | userId VARCHAR(32) NOT NULL, 9 | /* String's text. */ 10 | text VARCHAR(1024) NOT NULL, 11 | /* Date this string was created. */ 12 | createdAt DATETIME NOT NULL, 13 | PRIMARY KEY(id) 14 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 15 | -------------------------------------------------------------------------------- /schemas/userMasteryPairs.sql: -------------------------------------------------------------------------------- 1 | /* An entry for every mastery a user knows. */ 2 | CREATE TABLE userMasteryPairs ( 3 | 4 | /* Id of the user. FK into users. */ 5 | userId VARCHAR(32) NOT NULL, 6 | 7 | /* Id of the mastery. FK into pages. */ 8 | masteryId VARCHAR(32) NOT NULL, 9 | 10 | /* Date this entry was created. */ 11 | createdAt DATETIME NOT NULL, 12 | 13 | /* Date this entry was updated. */ 14 | updatedAt DATETIME NOT NULL, 15 | 16 | /* Set if the user has this mastery. */ 17 | has BOOLEAN NOT NULL, 18 | 19 | /* Set if the user wants to read this. */ 20 | wants BOOLEAN NOT NULL, 21 | 22 | /* Level of understanding. */ 23 | level INT NOT NULL, 24 | 25 | /* Id of the page where the user marked the mastery learned */ 26 | taughtBy VARCHAR(32) NOT NULL, 27 | 28 | /* User's trust when they learned the mastery. FK into userTrustSnapshots */ 29 | trustSnapshotId BIGINT NOT NULL, 30 | 31 | PRIMARY KEY(userId,masteryId) 32 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 33 | -------------------------------------------------------------------------------- /schemas/userPageObjectPairs.sql: -------------------------------------------------------------------------------- 1 | /* This table contains an entry for each (user, page object) pair, where 2 | the object can store some user specific data. For example, multiple choice 3 | questions can store the user's answer. */ 4 | CREATE TABLE userPageObjectPairs ( 5 | 6 | /* Id of the user the user is for. FK into users. */ 7 | userId VARCHAR(32) NOT NULL, 8 | 9 | /* Id of the page the info is for. */ 10 | pageId VARCHAR(32) NOT NULL, 11 | 12 | /* Page's published edit at the time this value was set. */ 13 | edit INT NOT NULL, 14 | 15 | /* Alias name of the object. */ 16 | object VARCHAR(64) NOT NULL, 17 | 18 | /* When this value was originally created at. */ 19 | createdAt DATETIME NOT NULL, 20 | 21 | /* When this value was updated. */ 22 | updatedAt DATETIME NOT NULL, 23 | 24 | /* Whatever value the object decides to set here. */ 25 | value VARCHAR(512) NOT NULL, 26 | 27 | PRIMARY KEY(userId,pageId,object) 28 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 29 | -------------------------------------------------------------------------------- /schemas/userRequisitePairSnapshots.sql: -------------------------------------------------------------------------------- 1 | /* When we snapshot all user's requisites, we store them in this table. Each snapshot 2 | has the same id, but takes up multiple rows. */ 3 | CREATE TABLE userRequisitePairSnapshots ( 4 | /* Id of the snapshot. Note that this is not unique per row. */ 5 | id BIGINT NOT NULL, 6 | /* Id of the user. FK into users. */ 7 | userId VARCHAR(32) NOT NULL, 8 | /* Id of the requisite. FK into pages. */ 9 | requisiteId VARCHAR(32) NOT NULL, 10 | /* Date this entry was created. */ 11 | createdAt DATETIME NOT NULL, 12 | /* Set if the user has this mastery. */ 13 | has BOOLEAN NOT NULL, 14 | /* Set if the user wants to read this. */ 15 | wants BOOLEAN NOT NULL 16 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 17 | -------------------------------------------------------------------------------- /schemas/userSubscriptions.sql: -------------------------------------------------------------------------------- 1 | /* This table contains all the subscriptions to users. */ 2 | CREATE TABLE userSubscriptions ( 3 | 4 | /* User id of the subscriber. FK into users. */ 5 | userId VARCHAR(32) NOT NULL, 6 | 7 | /* Id of the user this user is subscribed to. FK into users. */ 8 | toUserId VARCHAR(32) NOT NULL, 9 | 10 | /* When this subscription was created. */ 11 | createdAt DATETIME NOT NULL, 12 | 13 | PRIMARY KEY(userId, toUserId) 14 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 15 | -------------------------------------------------------------------------------- /schemas/visits.sql: -------------------------------------------------------------------------------- 1 | /* Each row is a page-user pair with the date and time 2 | when the user has last seen the page. */ 3 | CREATE TABLE visits ( 4 | 5 | /* If the user is logged in, user's id. FK into users. */ 6 | userId VARCHAR(64) NOT NULL, 7 | 8 | /* Session id. If the user is *not* logged in, the userId will be the same as this value. */ 9 | sessionId VARCHAR(64) NOT NULL, 10 | 11 | /* Analytics id. Base64-encoded Sha256 hash of the session id. */ 12 | analyticsId VARCHAR(64) NOT NULL, 13 | 14 | /* IP address of the user's computer. */ 15 | ipAddress VARCHAR(64) NOT NULL, 16 | 17 | /* Page id. FK into pages. */ 18 | pageId VARCHAR(32) NOT NULL, 19 | 20 | /* When this visit occured. */ 21 | createdAt DATETIME NOT NULL 22 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 23 | -------------------------------------------------------------------------------- /schemas/votes.sql: -------------------------------------------------------------------------------- 1 | /* An entry for every probability vote a user casts for a question. There could be 2 | multiple votes from one user for the same page. */ 3 | CREATE TABLE votes ( 4 | 5 | /* PK. Vote's unique id. */ 6 | id BIGINT NOT NULL AUTO_INCREMENT, 7 | 8 | /* Id of the user who voted. FK into users. */ 9 | userId VARCHAR(32) NOT NULL, 10 | 11 | /* Id of the page this vote is for. FK into pages. */ 12 | pageId VARCHAR(32) NOT NULL, 13 | 14 | /* Vote value. Special values are: 15 | -1: mu 16 | -2: no vote */ 17 | value TINYINT NOT NULL, 18 | 19 | /* Date this like was created. */ 20 | createdAt DATETIME NOT NULL, 21 | 22 | PRIMARY KEY(id) 23 | ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; 24 | -------------------------------------------------------------------------------- /scripts/build_gomon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Builds the "gomon" Docker image. 4 | # 5 | source init.sh || exit 6 | TARGET=containers/monitoring/ 7 | cp -v scripts/gcloud_bashrc.sh ${TARGET} 8 | cp -v config.yaml ${TARGET} 9 | # cp src/go/monitoring ${TARGET} 10 | sudo docker build -t "hkjn/gomon" ${TARGET} 11 | -------------------------------------------------------------------------------- /scripts/check_deps.go: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env gorun 2 | 3 | package main 4 | 5 | import ( 6 | "log" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | ) 11 | 12 | const depsFile = ".last_deps_update" 13 | 14 | func main() { 15 | log.SetFlags(0) 16 | depsFi, err := os.Stat(depsFile) 17 | if err != nil && !os.IsNotExist(err) { 18 | log.Fatal(err) 19 | } 20 | 21 | newerThan := func(path string) bool { 22 | fi, err := os.Stat(path) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | return depsFi.ModTime().After(fi.ModTime()) 27 | } 28 | 29 | if err == nil && 30 | newerThan("package.json") && 31 | newerThan(filepath.FromSlash("src/site/static/js/typings.json")) { 32 | return 33 | } 34 | 35 | cmd := exec.Command("npm", "install") 36 | cmd.Stdin = os.Stdin 37 | cmd.Stdout = os.Stdout 38 | cmd.Stderr = os.Stderr 39 | if err := cmd.Run(); err != nil { 40 | log.Fatal(err) 41 | } 42 | 43 | if f, err := os.Create(depsFile); err != nil { 44 | log.Printf("Failed to record dependency update. Future runs of stage.sh will start more slowly than necessary. Error from os.Create: %v", err) 45 | } else { 46 | f.Close() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /scripts/configs_identical.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Utility for checking whether the config.yaml is up to date, i.e. if 4 | # the decrypted config.yaml.pgp equals it in contents. 5 | # 6 | # If it does not, this either means that: 7 | # 1. more changes arrived in config.yaml.pgp since your config.yaml was decrypted 8 | # 2. you've changed config.yaml locally and haven't re-encrypted and 9 | # pushed the changes to config.yaml.pgp 10 | source init.sh || exit 11 | 12 | cp -iv config.yaml config.yaml.bak 13 | echo "Decrypting config.yaml.gpg -> config.yaml.." >&2 14 | decrypt_config.sh 15 | if diff config.yaml.bak config.yaml ; then 16 | echo "Identical." >&2 17 | rm config.yaml.bak 18 | exit 0 19 | else 20 | echo "The config.yaml file differs from your local copy, moved your changes to config.yaml.bak." >&2 21 | echo "Please merge and commit your changes, or remove if they're not needed." >&2 22 | exit 1 23 | fi 24 | -------------------------------------------------------------------------------- /scripts/create_daemon_user.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Sets up 'xelaiedaemon' user, which runs the monitoring 4 | # dashboards. The password needs to be entered interactively (TODO: 5 | # fix this, read from config.yaml), and should be the value in 6 | # config.vm.daemon.password. 7 | # 8 | # This script assumes that the environment on this machine is set up 9 | # correctly for your user, i.e. that you can run stage.sh / 10 | # run_monitoring.sh and everything works. 11 | # 12 | # Note that the xelaiedaemon will be able to authenticate as you to 13 | # gcloud since your credentials are copied over. TODO: use service 14 | # account here if it can be made to work. 15 | 16 | source init.sh || exit 17 | DAEMON=xelaiedaemon 18 | sudo adduser ${DAEMON} 19 | 20 | for t in ".bashrc" "go_appengine" "go" "google-cloud-sdk" "src"; do 21 | sudo cp -vr ~/${t} /home/${DAEMON}/ 22 | sudo chown -vR ${DAEMON}:${DAEMON} /home/${DAEMON}/${t} 23 | done 24 | 25 | -------------------------------------------------------------------------------- /scripts/create_db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Creates xelaie DB and tables on MySQL server at localhost. 4 | 5 | source init.sh || exit 6 | 7 | HOST=localhost 8 | 9 | read -r -p "This script will DROP ALL DB DATA and rebuild the database at ${HOST}. Is this your intent? [y/N] " response 10 | if [[ ! $response =~ ^([yY][eE][sS]|[yY])$ ]]; then 11 | exit 12 | fi 13 | 14 | DB_NAME=$(cfg mysql.database) 15 | DB_USER=$(cfg mysql.user) 16 | ROOT_PW=$(cfg mysql.root.password) 17 | USER_PW=$(cfg mysql.password) 18 | 19 | echo "Creating DB ${DB_NAME}@${HOST}.." 20 | mysql --host ${HOST} -u root -p"${ROOT_PW}" -e "DROP DATABASE IF EXISTS ${DB_NAME}; CREATE DATABASE IF NOT EXISTS ${DB_NAME} DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE ${DB_NAME};" 21 | 22 | echo "Creating user ${DB_USER}.." 23 | # Note that "GRANT" also creates the user, if necessary (no point in using "CREATE USER"): 24 | # http://justcheckingonall.wordpress.com/2011/07/31/create-user-if-not-exists/ 25 | mysql --host ${HOST} -u root -p"${ROOT_PW}" -e "GRANT ALL ON ${DB_NAME}.* TO '${DB_USER}'@'%' IDENTIFIED BY '${USER_PW}';" 26 | 27 | SCHEMAS=schemas/*.sql 28 | for f in $SCHEMAS; do 29 | echo "Importing schema ${f}.." 30 | cat ${f} | mysql --host ${HOST} -u ${DB_USER} -p${USER_PW} ${DB_NAME} 31 | done 32 | 33 | echo "All done." 34 | -------------------------------------------------------------------------------- /scripts/create_monitoring_vm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Creates the monitoring GCE VM from container manifest. 4 | # 5 | source init.sh || exit 6 | 7 | PROJECT_NAME="exemplary-cycle-688" 8 | INSTANCE="monitoring-5" 9 | ZONE="europe-west1-b" 10 | CONTAINER_MANIFEST="containers/monitoring.yaml" 11 | if [ ! -e ${CONTAINER_MANIFEST} ]; then 12 | echo "Missing manifest file ${CONTAINER_MANIFEST}." >&2 13 | exit 1 14 | fi 15 | 16 | # A list of container VM images can be gotten with gcloud compute 17 | # images list --project google-containers, via 18 | # https://cloud.google.com/compute/docs/containers/container_vms. 19 | gcloud compute --project ${PROJECT_NAME} instances create ${INSTANCE} \ 20 | --image "container-vm-v20140929" \ 21 | --image-project "google-containers" \ 22 | --metadata-from-file "google-container-manifest=${CONTAINER_MANIFEST}" \ 23 | --tags "http-server" \ 24 | --zone ${ZONE} \ 25 | --network "staging" \ 26 | --machine-type "f1-micro" \ 27 | --scopes "https://www.googleapis.com/auth/compute" "https://www.googleapis.com/auth/devstorage.read_only" 28 | 29 | echo "Sleeping 60s to allow VM to boot.." >&2 30 | sleep 60 31 | 32 | gcloud_bootstrap_init.sh ${INSTANCE} ${ZONE} 33 | echo "All done." >&2 34 | 35 | -------------------------------------------------------------------------------- /scripts/create_pages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Updates the tables with the schema changes. 4 | 5 | source init.sh || exit 6 | 7 | HOST=localhost 8 | 9 | DB_NAME=$(cfg mysql.database) 10 | DB_USER=$(cfg mysql.user) 11 | ROOT_PW=$(cfg mysql.root.password) 12 | USER_PW=$(cfg mysql.password) 13 | 14 | cat schemas/helpers/create_reqs.sql | mysql -f --host ${HOST} -u ${DB_USER} -p${USER_PW} ${DB_NAME} 15 | cat schemas/helpers/create_reqs2.sql | mysql -f --host ${HOST} -u ${DB_USER} -p${USER_PW} ${DB_NAME} 16 | 17 | echo "All done." 18 | -------------------------------------------------------------------------------- /scripts/db_shell.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Connects to DB with interactive mysql shell. 4 | # 5 | # CAUTION: The session has the same permissions as the app user. Use 6 | # with caution, especially on live. 7 | 8 | source init.sh || exit 9 | 10 | if [ "$#" -ne 1 ]; then 11 | echo "Usage: $0 [localhost|live]" 12 | exit 13 | fi 14 | 15 | if [ $1 == "localhost" ]; then 16 | HOST=localhost 17 | else 18 | HOST=$(cfg "mysql.${1}.address") 19 | fi 20 | 21 | mysql --host ${HOST} -u $(cfg mysql.user) -p$(cfg mysql.password) $(cfg mysql.database) 22 | -------------------------------------------------------------------------------- /scripts/decrypt_config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Decrypts config.yaml from config.yaml.gpg. 4 | 5 | gpg --output config.yaml --decrypt config.yaml.pgp 6 | 7 | chmod 600 config.yaml 8 | -------------------------------------------------------------------------------- /scripts/decrypt_config_remote.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Decrypts local config.yaml.gpg and saves it on remote host. 4 | # 5 | # This script allows your private GPG key to stay on your local 6 | # machine, but to be used to decrypt the config remotely. 7 | # 8 | # TODO: gpg-agent (https://www.gnupg.org/documentation/manuals/gnupg/Invoking-GPG_002dAGENT.html) 9 | # or keychain (http://funtoo.org/Package:Keychain) should be able to 10 | # forward GPG keys, if this can be made to work that might be a 11 | # cleaner setup. 12 | 13 | source init.sh || exit 14 | 15 | if [ "$#" -ne 2 ]; then 16 | echo "Usage: $0 [vm, e.g. 'monitoring'] [remote user]" 17 | exit 18 | fi 19 | 20 | INSTANCE=$(cfg "vm.${1}.instance") 21 | ZONE=$(cfg "vm.${1}.zone") 22 | REMOTE_USER=${2} 23 | FILE="src/xelaie/config.yaml" 24 | CONFIG=/home/${REMOTE_USER}/${FILE} 25 | 26 | echo "Decrypting config.yaml.pgp and copying to ${REMOTE_USER}@${HOST}:${CONFIG}.." 27 | gpg --decrypt config.yaml.pgp | gcloud compute ssh ${INSTANCE} \ 28 | --zone ${ZONE} --command \ 29 | "sudo sh -c 'cat - > ${CONFIG} && sudo chmod 600 ${CONFIG}' && sudo chown ${REMOTE_USER}:${REMOTE_USER} ${CONFIG}" 30 | -------------------------------------------------------------------------------- /scripts/deploy_ae.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copy over the config into the AE app's directory, since it otherwise 4 | # won't be copied over in the deployment. 5 | cp -v config.yaml src/site/ 6 | cp -v config.yaml src/queue_daemon/ 7 | 8 | # Update the queues 9 | #appcfg.py update_queues src/site/ 10 | 11 | npm run webpack -- --config prod.config.js || exit 12 | 13 | # Deploy the app. 14 | goapp deploy \ 15 | src/queue_daemon/module.yaml \ 16 | src/site/app.yaml 17 | -------------------------------------------------------------------------------- /scripts/deploy_queue.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copy over the config into the AE app's directory, since it otherwise 4 | # won't be copied over in the deployment. 5 | cp -v config.yaml src/go/queue_daemon/ 6 | 7 | # Deploy the app. 8 | goapp deploy src/go/queue_daemon/module.yaml 9 | -------------------------------------------------------------------------------- /scripts/encrypt_config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Encrypts config.yaml as config.yaml.pgp, decryptable by trusted GPG keys. 4 | # 5 | # Note that your local pgp keyring needs to know about everyone on the 6 | # --recipient line below before you'll be able to use their public 7 | # keys for encryption. Run `gpg --import keys/foo.asc` for the 8 | # relevant key foo.asc that you don't have to import. 9 | 10 | source init.sh || exit 11 | 12 | gpg --output config.yaml.pgp --encrypt --armor --recipient me@hkjn.me --recipient alexei@xelaie.com config.yaml 13 | -------------------------------------------------------------------------------- /scripts/file_watcher.patch: -------------------------------------------------------------------------------- 1 | diff --git a/google/appengine/tools/devappserver2/watcher_common.py b/google/appengine/tools/devappserver2/watcher_common.py 2 | index 6cf7fcd..7104b86 100644 3 | --- a/google/appengine/tools/devappserver2/watcher_common.py 4 | +++ b/google/appengine/tools/devappserver2/watcher_common.py 5 | @@ -21,7 +21,10 @@ 6 | import os 7 | 8 | # A prefix for files and directories that we should not watch at all. 9 | -_IGNORED_PREFIX = '.' 10 | +_IGNORED_PREFIX = ( 11 | + '.', 12 | + 'node_modules', 13 | +) 14 | # File suffixes that should be ignored. 15 | _IGNORED_FILE_SUFFIXES = ( 16 | # Python temporaries 17 | -------------------------------------------------------------------------------- /scripts/gcloud_bootstrap_init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Installs dependencies on a GCE VM to act as a monitoring host for 4 | # xelaie. 5 | # 6 | # This script copies over and executes gcloud_bootstrap.sh on the 7 | # remote VM. 8 | # 9 | # Prerequisites: 10 | # 0. gcloud SDK is installed locally. 11 | # 1. SSH agent has unlocked keys to the VM, so passwordless SSH is possible 12 | # (`ssh-add ~/.ssh/google_compute_engine or similar). 13 | 14 | source init.sh || exit 15 | 16 | if [ "$#" -ne 2 ]; then 17 | echo "Usage: $0 [instance name] [instance zone]" >&2 18 | exit 1 19 | fi 20 | 21 | INSTANCE=${1} 22 | ZONE=${2} 23 | 24 | echo "Bootstrapping ${INSTANCE} VM in zone ${ZONE}.." >&2 25 | 26 | gcloud compute copy-files \ 27 | scripts/gcloud_bashrc.sh \ 28 | ${INSTANCE}:.bashrc \ 29 | --zone ${ZONE} 30 | gcloud compute copy-files \ 31 | scripts/gcloud_bootstrap.sh \ 32 | ${INSTANCE}: \ 33 | --zone ${ZONE} 34 | 35 | # Include a small .emacs to display tabs sanely. 36 | gcloud compute ssh ${INSTANCE} \ 37 | --zone ${ZONE} \ 38 | --command "echo '(setq tab-width 2)' > .emacs" 39 | gcloud compute ssh ${INSTANCE} \ 40 | --zone ${ZONE} \ 41 | --command "~/gcloud_bootstrap.sh" 42 | -------------------------------------------------------------------------------- /scripts/import_keys.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Imports GPG keys from ../keys into local keyring. 4 | 5 | for k in keys/*.asc; do 6 | gpg --import $k 7 | done 8 | echo "All done." 9 | -------------------------------------------------------------------------------- /scripts/init.sh: -------------------------------------------------------------------------------- 1 | # Shared bash setup for scripts. 2 | 3 | # Fail if any command fails (returns != 0). 4 | set -e 5 | set -o pipefail 6 | 7 | # cfg returns a value loaded from config.yaml. 8 | function cfg() { 9 | if [ "$#" -ne 1 ]; then 10 | echo "Usage: $0 [value]" >&2 11 | return 1 12 | fi 13 | 14 | # if ! /usr/bin/env shyaml -h 2> /dev/null; then 15 | # echo "No shyaml installed. Try 'sudo pip install shyaml'." >&2 16 | # return 3 17 | # fi 18 | VAL=$(cat config.yaml | /usr/bin/env shyaml get-value $1) 19 | if [ -z "$VAL" ]; then 20 | echo "No value $1 in config.yaml. Buggy script? Or config.yaml has changed in the repo and you need to decrypt_config.sh again?" >&2 21 | return 4 22 | fi 23 | echo ${VAL} 24 | return 0 25 | } 26 | 27 | function check_config() { 28 | if [ ! -e "config.yaml" ]; then 29 | echo "No config.yaml present in root of repo. Did you decrypt it with decrypt_config.sh (if you have keys locally) or decrypt_config_remote.sh (if not)?" >&2 30 | exit 1 31 | fi 32 | } 33 | 34 | function check_git_hooks() { 35 | if [ ! -e ".git/hooks/pre-commit" ]; then 36 | echo "Missing git precommit hooks file (.git/hooks/pre-commit). Please run 'symlink_git_hooks.sh'." >&2 37 | exit 2 38 | elif [ ! -e ".git/hooks/pre-push" ]; then 39 | echo "Missing git prepush hooks file (.git/hooks/pre-push). Please run 'symlink_git_hooks.sh'." >&2 40 | exit 3 41 | fi 42 | } 43 | 44 | check_config 45 | check_git_hooks 46 | -------------------------------------------------------------------------------- /scripts/local_mysql.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Starts local mysql server. 4 | sudo /usr/bin/mysqld_safe --datadir='/var/lib/mysql' 5 | -------------------------------------------------------------------------------- /scripts/needs_gofmt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Checks if any files about to be committed need gofmt'ing. 4 | 5 | echo "Checking if any files need gofmt.." >&2 6 | IFS=" 7 | " 8 | if git rev-parse HEAD >/dev/null 2>&1; then 9 | FILES=$(git diff --cached --name-only | grep -e '\.go$'); 10 | else 11 | FILES=$(git ls-files -c | grep -e '\.go$'); 12 | fi 13 | for file in $FILES; do 14 | badfile="$(git --no-pager show :"$file" | gofmt -s -l)" 15 | if test -n "$badfile" ; then 16 | echo "git pre-commit check failed: file needs 'gofmt -s': $file" >&2 17 | exit 1 18 | fi 19 | done 20 | -------------------------------------------------------------------------------- /scripts/patch_appengine.sh: -------------------------------------------------------------------------------- 1 | patch -p1 -d $(dirname $(which goapp)) < scripts/file_watcher.patch 2 | -------------------------------------------------------------------------------- /scripts/pre-commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Precommit scripts for xelaie. 4 | 5 | if [ -z "${TRUST_ME_I_KNOW_WHAT_I_AM_DOING}" ]; then 6 | scripts/needs_gofmt.sh 7 | else # Allow for a failsafe. 8 | echo "Okay, if you say so. Skipping pre-commit checks. Have fun." >&2 9 | fi 10 | -------------------------------------------------------------------------------- /scripts/pre-push.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Prepush hooks for xelaie. 4 | 5 | if [ -z "${TRUST_ME_I_KNOW_WHAT_I_AM_DOING}" ]; then 6 | scripts/run_tests.sh 7 | else # Allow for a failsafe. 8 | echo "Okay, if you say so. Skipping pre-push checks. Have fun." >&2 9 | fi 10 | -------------------------------------------------------------------------------- /scripts/run_gomon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Runs the "gomon" Docker image. 4 | # 5 | source init.sh || exit 6 | 7 | sudo docker run -t -p 8083:8083 -p 8086:8086 hkjn/gomon:v1 /etc/init.d/influxdb start -------------------------------------------------------------------------------- /scripts/run_monitoring.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Runs monitoring jobs on localhost. 4 | source init.sh || exit 5 | 6 | go build zanaduu3/src/monitoring/dash/xelaiemon 7 | ./xelaiemon -alsologtostderr 8 | -------------------------------------------------------------------------------- /scripts/run_prober.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Runs the prober agains rewards.xelaie.com locally. 4 | go build src/go/prober/prober.go 5 | ./prober -alsologtostderr 6 | -------------------------------------------------------------------------------- /scripts/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Runs all tests. 4 | # echo "Running all Go tests.." >&2 5 | # goapp test ./... 6 | echo "run_tests.sh is temporarily disabled" >&2 7 | -------------------------------------------------------------------------------- /scripts/style_js.sh: -------------------------------------------------------------------------------- 1 | # Go up until we are in /zanaduu3 directory 2 | function cdroot() 3 | { 4 | while [[ $PWD != '/' && ${PWD##*/} != 'zanaduu3' ]]; do cd ..; done 5 | } 6 | cdroot 7 | 8 | FILE_PATH=src/site/static/js 9 | jscs --fix --config ${FILE_PATH}/.jscsrc ${FILE_PATH} 10 | -------------------------------------------------------------------------------- /scripts/symlink_git_hooks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Symlinks 4 | 5 | cd .git/hooks/ 6 | ln -s ../../scripts/pre-commit.sh pre-commit 7 | ln -s ../../scripts/pre-push.sh pre-push 8 | -------------------------------------------------------------------------------- /scripts/update_db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Updates the tables with the schema changes. 4 | 5 | source init.sh || exit 6 | 7 | HOST=localhost 8 | 9 | DB_NAME=$(cfg mysql.database) 10 | DB_USER=$(cfg mysql.user) 11 | ROOT_PW=$(cfg mysql.root.password) 12 | USER_PW=$(cfg mysql.password) 13 | 14 | cat schemas/helpers/changes.sql | mysql -f --host ${HOST} -u ${DB_USER} -p${USER_PW} ${DB_NAME} 15 | 16 | echo "All done." 17 | -------------------------------------------------------------------------------- /scripts/update_vm_manifest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Updates the monitoring VM from container manifest. 4 | # 5 | CONTAINER_MANIFEST="containers/monitoring.yaml" 6 | ZONE="europe-west1-a" 7 | if [ ! -e ${CONTAINER_MANIFEST} ]; then 8 | echo "Missing manifest file ${CONTAINER_MANIFEST}." >&2 9 | exit 1 10 | fi 11 | 12 | gcloud compute instances add-metadata monitoring \ 13 | --metadata-from-file "google-container-manifest=${CONTAINER_MANIFEST}" \ 14 | --zone ${ZONE} 15 | echo "All done." >&2 16 | -------------------------------------------------------------------------------- /scripts/v2_stage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Stages the App Engine website locally. 4 | 5 | source init.sh || exit 6 | 7 | check_deps.go || exit 8 | 9 | # Copy over the config into the AE app's directory, since we need it 10 | # there for deployment. 11 | cp -v config.yaml src/v2/ 12 | 13 | # Start dev server, allowing access from *any* IP address. (Don't run 14 | # this if such access is undesirable, e.g. if your machine's IP is 15 | # publicly accessible and pages that you're working on that are super 16 | # secret aren't properly guarded by other auth mechanisms). 17 | dev_appserver.py \ 18 | src/v2/app.yaml \ 19 | --admin_port 8011 --port 8012 --host 0.0.0.0 --enable_sendmail=yes & 20 | appserver_PID=$! 21 | 22 | # Start webpack-dev-server to serve webpack bundles. The dev server 23 | # will watch for updates to files that the bundles depends on and 24 | # hot-reload them in the browser. 25 | # 26 | # Keep the port in sync with pageHandler.go. 27 | npm run webpack-dev-server -- \ 28 | --inline \ 29 | --progress \ 30 | --color \ 31 | --port 8014 \ 32 | --output-public-path "http://localhost:8014/static/js/" \ 33 | --hot \ 34 | --config dev.config.js \ 35 | & 36 | webpack_server_PID=$! 37 | 38 | # Kill both dev servers on ctrl-c. 39 | trap ctrl_c INT 40 | function ctrl_c() { 41 | kill $appserver_PID 42 | kill $webpack_server_PID 43 | } 44 | 45 | # https://stackoverflow.com/questions/2935183/bash-infinite-sleep-infinite-blocking 46 | cat 47 | -------------------------------------------------------------------------------- /src/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | } -------------------------------------------------------------------------------- /src/config/config_test.go: -------------------------------------------------------------------------------- 1 | // Tests for config handling 2 | package config 3 | 4 | import ( 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestLoad(t *testing.T) { 10 | c, err := load("../../config.yaml") 11 | if err != nil { 12 | t.Fatalf("Load() failed: %v\n", err) 13 | } 14 | if (reflect.DeepEqual(c, Config{})) { 15 | t.Fatalf("Load() returned empty Config") 16 | } 17 | if (c.MySQL == Config{}.MySQL) { 18 | t.Fatalf("Load() returned empty Config.MySQL") 19 | } 20 | if (c.Site == Config{}.Site) { 21 | t.Fatalf("Load() returned empty Config.Site") 22 | } 23 | if (c.VM == Config{}.VM) { 24 | t.Fatalf("Load() returned empty Config.Vm") 25 | } 26 | if (reflect.DeepEqual(c.Monitoring, Config{}.Monitoring)) { 27 | t.Fatalf("Load() returned empty Config.Monitoring") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/config/init.go: -------------------------------------------------------------------------------- 1 | // Package config provides a wrapper around config.yaml 2 | package config 3 | 4 | import "log" 5 | 6 | var ( 7 | // We unfortunately must carry around a copy of the config.yaml 8 | // (canonically in the top directory of the repo), as AppEngine 9 | // otherwise won't be smart enough to deploy it to live. (It will 10 | // work on dev, but break the live site.) 11 | XC = Load() 12 | ) 13 | 14 | func init() { 15 | if XC.Site.Session.Auth == "" { 16 | log.Fatalf("FATAL: missing site.session.auth value in config.yaml - can't use encrypted cookies!\n") 17 | } 18 | if XC.Site.Session.Crypt == "" { 19 | log.Fatalf("FATAL: missing site.session.crypt value in config.yaml - can't use encrypted cookies!\n") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/queue_daemon/module.yaml: -------------------------------------------------------------------------------- 1 | application: zanaduu3 2 | module: daemon 3 | version: 002d 4 | runtime: go 5 | api_version: go1 6 | manual_scaling: 7 | instances: 1 8 | instance_class: B1 9 | 10 | handlers: 11 | - url: /.* 12 | script: _go_app 13 | -------------------------------------------------------------------------------- /src/sessions/creds.go: -------------------------------------------------------------------------------- 1 | // creds.go: handles credentials 2 | package sessions 3 | 4 | import ( 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/garyburd/go-oauth/oauth" 9 | ) 10 | 11 | var ( 12 | credsKey = "credentials" // key for session storage 13 | emptyCreds = Credentials{} 14 | FakeCreds = Credentials{&oauth.Credentials{"FAKE_TOKEN", "FAKE_SECRET"}} 15 | ) 16 | 17 | type Credentials struct { 18 | *oauth.Credentials 19 | } 20 | 21 | // Save stores the credentials in session. 22 | func (creds *Credentials) Save(w http.ResponseWriter, r *http.Request) error { 23 | s, err := GetSession(r) 24 | if err != nil { 25 | return fmt.Errorf("couldn't get session: %v", err) 26 | } 27 | 28 | s.Values[credsKey] = *creds 29 | err = s.Save(r, w) 30 | if err != nil { 31 | return fmt.Errorf("failed to save credentials key to session: %v", err) 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /src/sessions/error.go: -------------------------------------------------------------------------------- 1 | // error.go defined our own error type 2 | package sessions 3 | 4 | import ( 5 | "fmt" 6 | ) 7 | 8 | type Error *internalError 9 | 10 | // internalError encompasses various kinds of error states we can have 11 | type internalError struct { 12 | // Technical error that happend (e.g. query failed) 13 | Err error 14 | // Message to send to the user 15 | Message string 16 | } 17 | 18 | func NewError(message string, err error) Error { 19 | return &internalError{Message: message, Err: err} 20 | } 21 | 22 | func PassThrough(err error) Error { 23 | if err == nil { 24 | return nil 25 | } 26 | return &internalError{Err: err} 27 | } 28 | 29 | func ToError(e Error) error { 30 | return fmt.Errorf("%s: %v", e.Message, e.Err) 31 | } 32 | -------------------------------------------------------------------------------- /src/site/app.yaml: -------------------------------------------------------------------------------- 1 | application: zanaduu3 2 | version: 002d 3 | runtime: go 4 | api_version: go1 5 | automatic_scaling: 6 | min_idle_instances: 3 7 | max_pending_latency: 0.5s 8 | instance_class: F2 9 | 10 | skip_files: 11 | # AppEngine defaults 12 | - ^(.*/)?#.*#$ 13 | - ^(.*/)?.*~$ 14 | - ^(.*/)?.*\.py[co]$ 15 | - ^(.*/)?.*/RCS/.*$ 16 | - ^(.*/)?\..*$ 17 | # Our additions 18 | - node_modules 19 | 20 | handlers: 21 | - url: /static 22 | static_dir: static 23 | secure: always 24 | 25 | - url: /favicon.ico 26 | static_files: static/icons/favicon.ico 27 | upload: static/icons/favicon.ico 28 | secure: always 29 | 30 | - url: /apple-touch-icon-precomposed.png 31 | static_files: static/images/arbital-icon-120.png 32 | upload: static/images/arbital-icon-120.png 33 | secure: always 34 | 35 | - url: /apple-touch-icon.png 36 | static_files: static/images/arbital-icon-120.png 37 | upload: static/images/arbital-icon-120.png 38 | secure: always 39 | 40 | - url: /.* 41 | script: _go_app 42 | secure: always 43 | 44 | - url: /daemon 45 | script: _go_app 46 | login: admin 47 | -------------------------------------------------------------------------------- /src/site/defaultJsonHandler.go: -------------------------------------------------------------------------------- 1 | // defaultJsonHandler.go returns basic data every page needs. Used for pages 2 | // that don't need any custom data, and therefore don't have custom handlers. 3 | 4 | package site 5 | 6 | import ( 7 | "zanaduu3/src/core" 8 | "zanaduu3/src/pages" 9 | ) 10 | 11 | var defaultHandler = siteHandler{ 12 | URI: "/json/default/", 13 | HandlerFunc: defaultJSONHandlerFunc, 14 | Options: pages.PageOptions{ 15 | AllowAnyone: true, 16 | }, 17 | } 18 | 19 | func defaultJSONHandlerFunc(params *pages.HandlerParams) *pages.Result { 20 | db := params.DB 21 | 22 | returnData := core.NewHandlerData(params.U).SetResetEverything() 23 | err := core.ExecuteLoadPipeline(db, returnData) 24 | if err != nil { 25 | return pages.Fail("Pipeline error", err) 26 | } 27 | 28 | return pages.Success(returnData) 29 | } 30 | -------------------------------------------------------------------------------- /src/site/dismissUpdateHandler.go: -------------------------------------------------------------------------------- 1 | // Handles requests to dismiss updates 2 | 3 | package site 4 | 5 | import ( 6 | "encoding/json" 7 | "net/http" 8 | 9 | "zanaduu3/src/pages" 10 | ) 11 | 12 | type dismissUpdateData struct { 13 | UpdateID string `json:"id"` 14 | } 15 | 16 | var dismissUpdateHandler = siteHandler{ 17 | URI: "/dismissUpdate/", 18 | HandlerFunc: dismissUpdateHandlerFunc, 19 | Options: pages.PageOptions{ 20 | RequireLogin: true, 21 | }, 22 | } 23 | 24 | func dismissUpdateHandlerFunc(params *pages.HandlerParams) *pages.Result { 25 | db := params.DB 26 | u := params.U 27 | 28 | // Decode data 29 | var data dismissUpdateData 30 | err := json.NewDecoder(params.R.Body).Decode(&data) 31 | if err != nil { 32 | return pages.Fail("Couldn't decode request", err).Status(http.StatusBadRequest) 33 | } 34 | 35 | // Dismiss the update 36 | statement := db.NewStatement(` 37 | UPDATE updates 38 | SET dismissed=TRUE 39 | WHERE id=? AND userId=?`) 40 | if _, err := statement.Exec(data.UpdateID, u.ID); err != nil { 41 | return pages.Fail("Couldn't dismiss update", err) 42 | } 43 | 44 | return pages.Success(nil) 45 | } 46 | -------------------------------------------------------------------------------- /src/site/domainPageJsonHandler.go: -------------------------------------------------------------------------------- 1 | // domainPageJsonHandler.go serves data to display domain index page. 2 | 3 | package site 4 | 5 | import ( 6 | "encoding/json" 7 | "net/http" 8 | 9 | "zanaduu3/src/core" 10 | "zanaduu3/src/pages" 11 | ) 12 | 13 | type domainPageData struct { 14 | DomainAlias string 15 | } 16 | 17 | var domainPageHandler = siteHandler{ 18 | URI: "/json/domainPage/", 19 | HandlerFunc: domainPageHandlerFunc, 20 | Options: pages.PageOptions{}, 21 | } 22 | 23 | // domainPageJsonHandler handles the request. 24 | func domainPageHandlerFunc(params *pages.HandlerParams) *pages.Result { 25 | db := params.DB 26 | u := params.U 27 | returnData := core.NewHandlerData(u).SetResetEverything() 28 | 29 | // Decode data 30 | var data domainPageData 31 | decoder := json.NewDecoder(params.R.Body) 32 | err := decoder.Decode(&data) 33 | if err != nil { 34 | return pages.Fail("Couldn't decode request", err).Status(http.StatusBadRequest) 35 | } 36 | 37 | returnData.ResultMap["domain"], err = core.LoadDomainByAlias(db, data.DomainAlias) 38 | if err != nil { 39 | return pages.Fail("Couldn't load domain", err) 40 | } 41 | 42 | return pages.Success(returnData) 43 | } 44 | -------------------------------------------------------------------------------- /src/site/dynamicPage.go: -------------------------------------------------------------------------------- 1 | // dynamicPage.go serves a page which then loads more data dynamically. 2 | 3 | package site 4 | 5 | import ( 6 | "zanaduu3/src/pages" 7 | ) 8 | 9 | var ( 10 | dynamicPage = newPage(dynamicPageRenderer, dynamicTmpls) 11 | ) 12 | 13 | // dynamicPageRenderer renders the dynamic page. 14 | func dynamicPageRenderer(params *pages.HandlerParams) *pages.Result { 15 | return pages.Success(nil) 16 | } 17 | -------------------------------------------------------------------------------- /src/site/feedbackHandler.go: -------------------------------------------------------------------------------- 1 | // feedbackHandler.go adds a new vote for for a page. 2 | 3 | package site 4 | 5 | import ( 6 | "encoding/json" 7 | "net/http" 8 | 9 | "zanaduu3/src/pages" 10 | "zanaduu3/src/tasks" 11 | ) 12 | 13 | // feedbackData contains data given to us in the request. 14 | type feedbackData struct { 15 | Text string 16 | } 17 | 18 | var feedbackHandler = siteHandler{ 19 | URI: "/feedback/", 20 | HandlerFunc: feedbackHandlerFunc, 21 | Options: pages.PageOptions{ 22 | RequireLogin: true, 23 | }, 24 | } 25 | 26 | // feedbackHandlerFunc handles requests to create/update a prior vote. 27 | func feedbackHandlerFunc(params *pages.HandlerParams) *pages.Result { 28 | u := params.U 29 | c := params.C 30 | 31 | decoder := json.NewDecoder(params.R.Body) 32 | var data feedbackData 33 | err := decoder.Decode(&data) 34 | if err != nil { 35 | return pages.Fail("Couldn't decode json", err).Status(http.StatusBadRequest) 36 | } 37 | if data.Text == "" { 38 | return pages.Fail("No text specified", nil).Status(http.StatusBadRequest) 39 | } 40 | 41 | var task tasks.SendFeedbackEmailTask 42 | task.UserID = u.ID 43 | task.UserEmail = u.Email 44 | task.Text = data.Text 45 | if err := tasks.Enqueue(c, &task, nil); err != nil { 46 | c.Errorf("Couldn't enqueue a task: %v", err) 47 | } 48 | 49 | return pages.Success(nil) 50 | } 51 | -------------------------------------------------------------------------------- /src/site/forgotPasswordHandler.go: -------------------------------------------------------------------------------- 1 | // forgotPasswordHandler.go handles requests when the user says they forgot their password 2 | 3 | package site 4 | 5 | import ( 6 | "encoding/json" 7 | "net/http" 8 | 9 | "zanaduu3/src/okta" 10 | "zanaduu3/src/pages" 11 | ) 12 | 13 | // forgotPasswordHandlerData is the data received from the request. 14 | type forgotPasswordHandlerData struct { 15 | Email string 16 | } 17 | 18 | var forgotPasswordHandler = siteHandler{ 19 | URI: "/json/forgotPassword/", 20 | HandlerFunc: forgotPasswordHandlerFunc, 21 | Options: pages.PageOptions{ 22 | AllowAnyone: true, 23 | }, 24 | } 25 | 26 | func forgotPasswordHandlerFunc(params *pages.HandlerParams) *pages.Result { 27 | decoder := json.NewDecoder(params.R.Body) 28 | var data forgotPasswordHandlerData 29 | err := decoder.Decode(&data) 30 | if err != nil { 31 | return pages.Fail("Couldn't decode json", err).Status(http.StatusBadRequest) 32 | } 33 | if len(data.Email) <= 0 { 34 | return pages.Fail("Email is not set", nil).Status(http.StatusBadRequest) 35 | } 36 | 37 | err = okta.ForgotPassword(params.C, data.Email) 38 | if err != nil { 39 | return pages.Fail("Invalid email or password", err) 40 | } 41 | 42 | return pages.Success(nil) 43 | } 44 | -------------------------------------------------------------------------------- /src/site/lensJsonHandler.go: -------------------------------------------------------------------------------- 1 | // lensJsonHandler.go contains the handler for returning JSON with data to display a lens. 2 | 3 | package site 4 | 5 | import ( 6 | "encoding/json" 7 | "net/http" 8 | 9 | "zanaduu3/src/core" 10 | "zanaduu3/src/pages" 11 | ) 12 | 13 | // lensJsonData contains parameters passed in via the request. 14 | type lensJSONData struct { 15 | PageAlias string 16 | } 17 | 18 | var lensHandler = siteHandler{ 19 | URI: "/json/lens/", 20 | HandlerFunc: lensJSONHandler, 21 | } 22 | 23 | // lensJsonHandler handles the request. 24 | func lensJSONHandler(params *pages.HandlerParams) *pages.Result { 25 | u := params.U 26 | db := params.DB 27 | returnData := core.NewHandlerData(u) 28 | 29 | // Decode data 30 | var data lensJSONData 31 | decoder := json.NewDecoder(params.R.Body) 32 | err := decoder.Decode(&data) 33 | if err != nil { 34 | return pages.Fail("Couldn't decode request", err).Status(http.StatusBadRequest) 35 | } 36 | 37 | // Get actual page id 38 | pageID, ok, err := core.LoadAliasToPageID(db, u, data.PageAlias) 39 | if err != nil { 40 | return pages.Fail("Couldn't convert alias", err) 41 | } 42 | if !ok { 43 | return pages.Fail("Couldn't find page", err) 44 | } 45 | 46 | // Load data 47 | core.AddPageToMap(pageID, returnData.PageMap, core.LensFullLoadOptions) 48 | err = core.ExecuteLoadPipeline(db, returnData) 49 | if err != nil { 50 | pages.Fail("Pipeline error", err) 51 | } 52 | 53 | return pages.Success(returnData) 54 | } 55 | -------------------------------------------------------------------------------- /src/site/mailchimpSignupHandler.go: -------------------------------------------------------------------------------- 1 | // mailchimpSignupPage.go serves the mailchimpSignup page. 2 | 3 | package site 4 | 5 | import ( 6 | "encoding/json" 7 | "net/http" 8 | 9 | "zanaduu3/src/mailchimp" 10 | "zanaduu3/src/pages" 11 | ) 12 | 13 | // mailchimpSignupHandlerData is the data received from the request. 14 | type mailchimpSignupHandlerData struct { 15 | Email string 16 | Interests map[string]bool 17 | } 18 | 19 | var mailchimpSignupHandler = siteHandler{ 20 | URI: "/mailchimpSignup/", 21 | HandlerFunc: mailchimpSignupHandlerFunc, 22 | Options: pages.PageOptions{}, 23 | } 24 | 25 | func mailchimpSignupHandlerFunc(params *pages.HandlerParams) *pages.Result { 26 | decoder := json.NewDecoder(params.R.Body) 27 | var data mailchimpSignupHandlerData 28 | err := decoder.Decode(&data) 29 | if err != nil { 30 | return pages.Fail("Couldn't decode json", err).Status(http.StatusBadRequest) 31 | } 32 | if len(data.Email) <= 0 { 33 | return pages.Fail("Email have to be specified", nil).Status(http.StatusBadRequest) 34 | } 35 | 36 | account := &mailchimp.Account{ 37 | Email: data.Email, 38 | Interests: data.Interests, 39 | } 40 | 41 | // Execute request 42 | err = mailchimp.SubscribeUser(params.C, account) 43 | if err != nil { 44 | return pages.Fail("Couldn't subscribe user", err) 45 | } 46 | return pages.Success(nil) 47 | } 48 | -------------------------------------------------------------------------------- /src/site/marksJsonHandler.go: -------------------------------------------------------------------------------- 1 | // marksJsonHandler.go returns marks for a given page. 2 | 3 | package site 4 | 5 | import ( 6 | "encoding/json" 7 | "net/http" 8 | 9 | "zanaduu3/src/core" 10 | "zanaduu3/src/pages" 11 | ) 12 | 13 | // marksJsonData contains parameters passed in via the request. 14 | type marksJSONData struct { 15 | PageID string 16 | } 17 | 18 | var marksHandler = siteHandler{ 19 | URI: "/json/marks/", 20 | HandlerFunc: marksJSONHandler, 21 | Options: pages.PageOptions{ 22 | RequireLogin: true, 23 | }, 24 | } 25 | 26 | // marksJsonHandler handles the request. 27 | func marksJSONHandler(params *pages.HandlerParams) *pages.Result { 28 | db := params.DB 29 | returnData := core.NewHandlerData(params.U) 30 | 31 | // Decode data 32 | var data marksJSONData 33 | err := json.NewDecoder(params.R.Body).Decode(&data) 34 | if err != nil { 35 | return pages.Fail("Couldn't decode request", err).Status(http.StatusBadRequest) 36 | } 37 | if !core.IsIDValid(data.PageID) { 38 | return pages.Fail("Need a valid pageId", err).Status(http.StatusBadRequest) 39 | } 40 | 41 | // Load the marks 42 | loadOptions := &core.PageLoadOptions{ 43 | AllMarks: true, 44 | } 45 | core.AddPageToMap(data.PageID, returnData.PageMap, loadOptions) 46 | err = core.ExecuteLoadPipeline(db, returnData) 47 | if err != nil { 48 | return pages.Fail("Couldn't load pages", err) 49 | } 50 | return pages.Success(returnData) 51 | } 52 | -------------------------------------------------------------------------------- /src/site/newsletterJsonHandler.go: -------------------------------------------------------------------------------- 1 | // newsletterJsonHandler.go serves the newsletter page data. 2 | 3 | package site 4 | 5 | import ( 6 | "zanaduu3/src/core" 7 | "zanaduu3/src/mailchimp" 8 | "zanaduu3/src/pages" 9 | ) 10 | 11 | var newsletterHandler = siteHandler{ 12 | URI: "/json/newsletter/", 13 | HandlerFunc: newsletterJSONHandler, 14 | Options: pages.PageOptions{}, 15 | } 16 | 17 | func newsletterJSONHandler(params *pages.HandlerParams) *pages.Result { 18 | u := params.U 19 | c := params.C 20 | returnData := core.NewHandlerData(u).SetResetEverything() 21 | var err error 22 | 23 | if u.Email != "" { 24 | u.MailchimpInterests, err = mailchimp.GetInterests(c, u.Email) 25 | if err != nil { 26 | return pages.Fail("Couldn't load mailchimp subscriptions", err) 27 | } 28 | } 29 | 30 | return pages.Success(returnData) 31 | } 32 | -------------------------------------------------------------------------------- /src/site/queue.yaml: -------------------------------------------------------------------------------- 1 | # Set the total storage limit for all queues to the free app limit. 2 | # total_storage_limit: 500M 3 | queue: 4 | - name: daemonQueue 5 | mode: pull 6 | # - name: report-monitoring 7 | # mode: push 8 | # bucket_size: 100 9 | # rate: 100/s 10 | -------------------------------------------------------------------------------- /src/site/recentlyCreatedCommentJsonHandler.go: -------------------------------------------------------------------------------- 1 | // Serves JSON for most recently created comments 2 | 3 | package site 4 | 5 | import ( 6 | "zanaduu3/src/core" 7 | "zanaduu3/src/database" 8 | "zanaduu3/src/pages" 9 | ) 10 | 11 | var recentlyCreatedCommentHandler = siteHandler{ 12 | URI: "/json/recentlyCreatedComment/", 13 | HandlerFunc: recentlyCreatedCommentJSONHandler, 14 | Options: pages.PageOptions{ 15 | RequireLogin: true, 16 | }, 17 | } 18 | 19 | const RecentlyCreatedCommentIdsHandlerType = "recentlyCreatedCommentIds" 20 | 21 | func recentlyCreatedCommentJSONHandler(params *pages.HandlerParams) *pages.Result { 22 | return DashboardListJSONHandler(params, LoadRecentlyCreatedComment, RecentlyCreatedCommentIdsHandlerType) 23 | } 24 | 25 | func LoadRecentlyCreatedComment(db *database.DB, returnData *core.CommonHandlerData, privateDomainID string, numToLoad int, 26 | _ *core.PageLoadOptions) ([]string, error) { 27 | // Load recently created by me comment ids 28 | rows := database.NewQuery(` 29 | SELECT p.pageId 30 | FROM pages AS p 31 | JOIN pageInfos AS pi 32 | ON (p.pageId=pi.pageId && p.edit=pi.currentEdit) 33 | WHERE p.creatorId=?`, returnData.User.ID).Add(` 34 | AND pi.seeDomainId=?`, privateDomainID).Add(` 35 | AND pi.type=?`, core.CommentPageType).Add(` 36 | AND`).AddPart(core.PageInfosFilter(returnData.User)).Add(` 37 | ORDER BY pi.createdAt DESC 38 | LIMIT ?`, numToLoad).ToStatement(db).Query() 39 | return core.LoadPageIDs(rows, returnData.PageMap, core.TitlePlusLoadOptions) 40 | } 41 | -------------------------------------------------------------------------------- /src/site/recentlyEditedJsonHandler.go: -------------------------------------------------------------------------------- 1 | // Serves JSON for recently edited pages 2 | 3 | package site 4 | 5 | import ( 6 | "zanaduu3/src/core" 7 | "zanaduu3/src/database" 8 | "zanaduu3/src/pages" 9 | ) 10 | 11 | var recentlyEditedHandler = siteHandler{ 12 | URI: "/json/recentlyEdited/", 13 | HandlerFunc: recentlyEditedJSONHandler, 14 | Options: pages.PageOptions{ 15 | RequireLogin: true, 16 | }, 17 | } 18 | 19 | const RecentlyEditedIdsHandlerType = "recentlyEditedIds" 20 | 21 | func recentlyEditedJSONHandler(params *pages.HandlerParams) *pages.Result { 22 | return DashboardListJSONHandler(params, LoadRecentlyEdited, RecentlyEditedIdsHandlerType) 23 | } 24 | 25 | func LoadRecentlyEdited(db *database.DB, returnData *core.CommonHandlerData, privateDomainID string, numToLoad int, 26 | pageOptions *core.PageLoadOptions) ([]string, error) { 27 | // Load recently created and edited by me page ids 28 | rows := database.NewQuery(` 29 | SELECT p.pageId 30 | FROM pages AS p 31 | JOIN pageInfos AS pi 32 | ON (p.pageId=pi.pageId) 33 | WHERE p.creatorId=?`, returnData.User.ID).Add(` 34 | AND pi.seeDomainId=?`, privateDomainID).Add(` 35 | AND pi.type!=?`, core.CommentPageType).Add(` 36 | AND`).AddPart(core.PageInfosFilter(returnData.User)).Add(` 37 | GROUP BY 1 38 | ORDER BY MAX(p.createdAt) DESC 39 | LIMIT ?`, numToLoad).ToStatement(db).Query() 40 | return core.LoadPageIDs(rows, returnData.PageMap, pageOptions) 41 | } 42 | -------------------------------------------------------------------------------- /src/site/requisitesJsonHandler.go: -------------------------------------------------------------------------------- 1 | // requisitesJsonHandler.go returns all the requisites the user knows 2 | 3 | package site 4 | 5 | import ( 6 | "zanaduu3/src/core" 7 | "zanaduu3/src/pages" 8 | ) 9 | 10 | var requisitesHandler = siteHandler{ 11 | URI: "/json/requisites/", 12 | HandlerFunc: requisitesJSONHandler, 13 | Options: pages.PageOptions{}, 14 | } 15 | 16 | func requisitesJSONHandler(params *pages.HandlerParams) *pages.Result { 17 | u := params.U 18 | db := params.DB 19 | returnData := core.NewHandlerData(u).SetResetEverything() 20 | 21 | // Options to load the pages with 22 | pageOptions := (&core.PageLoadOptions{}).Add(core.TitlePlusLoadOptions) 23 | 24 | // Load all masteries 25 | rows := db.NewStatement(` 26 | SELECT masteryId 27 | FROM userMasteryPairs 28 | WHERE userId=?`).Query(u.GetSomeID()) 29 | _, err := core.LoadPageIDs(rows, returnData.PageMap, pageOptions) 30 | if err != nil { 31 | return pages.Fail("Error while loading masteries", err) 32 | } 33 | 34 | // Load pages. 35 | err = core.ExecuteLoadPipeline(db, returnData) 36 | if err != nil { 37 | return pages.Fail("Pipeline error", err) 38 | } 39 | 40 | return pages.Success(returnData) 41 | } 42 | -------------------------------------------------------------------------------- /src/site/settingsPageJsonHandler.go: -------------------------------------------------------------------------------- 1 | // settingsPageJsonHandler.go contains the handler for returning JSON with data 2 | // to display the settings/invite page. 3 | 4 | package site 5 | 6 | import ( 7 | "zanaduu3/src/core" 8 | "zanaduu3/src/pages" 9 | ) 10 | 11 | type Domain struct { 12 | DomainID string `json:"domainId"` 13 | LongName string `json:"longName"` 14 | } 15 | 16 | var settingsPageHandler = siteHandler{ 17 | URI: "/json/settingsPage/", 18 | HandlerFunc: settingsPageJSONHandler, 19 | Options: pages.PageOptions{ 20 | RequireLogin: true, 21 | }, 22 | } 23 | 24 | // settingsPageJsonHandler renders the settings page. 25 | func settingsPageJSONHandler(params *pages.HandlerParams) *pages.Result { 26 | db := params.DB 27 | u := params.U 28 | returnData := core.NewHandlerData(u).SetResetEverything() 29 | 30 | err := core.ExecuteLoadPipeline(db, returnData) 31 | if err != nil { 32 | return pages.Fail("Pipeline error", err) 33 | } 34 | 35 | return pages.Success(returnData) 36 | } 37 | -------------------------------------------------------------------------------- /src/site/static/html/adminDashboardPage.html: -------------------------------------------------------------------------------- 1 |
15 | 16 | | 17 |
What are you interested in?
21 |Links:
` 18 | doc, err := html.Parse(strings.NewReader(s)) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | var f func(*html.Node) 23 | f = func(n *html.Node) { 24 | if n.Type == html.ElementNode && n.Data == "a" { 25 | for _, a := range n.Attr { 26 | if a.Key == "href" { 27 | fmt.Println(a.Val) 28 | break 29 | } 30 | } 31 | } 32 | for c := n.FirstChild; c != nil; c = c.NextSibling { 33 | f(c) 34 | } 35 | } 36 | f(doc) 37 | // Output: 38 | // foo 39 | // /bar/baz 40 | } 41 | -------------------------------------------------------------------------------- /vendor/golang.org/x/net/html/testdata/webkit/adoption02.dat: -------------------------------------------------------------------------------- 1 | #data 2 | 1234 3 | #errors 4 | #document 5 | | 6 | |
7 | | 8 | | 9 | | "1" 10 | | 11 | | "2" 12 | | 13 | |