├── db ├── 20190218183556-v1.6.1.sql ├── 20180724125115-remove-config.sql ├── 20181007231407-v1.1.4.sql ├── 20181228114101-v1.4.0.sql ├── 20181228114101-v1.4.1.sql ├── 20190123002724-v1.4.2.sql ├── 20190204180609-v1.5.0.sql ├── 20190218173502-v1.6.0.sql ├── 20190219001130-v1.6.2.sql ├── 20190501201032-v1.7.0.sql ├── 20190508222848-reset-count.sql ├── 20180610215858-commenter-password.sql ├── 20181218183803-sticky-comments.sql ├── 20180620083655-session-token-renamme.sql ├── 20181007230906-store-version.sql ├── 20190122235525-anonymous-moderation-default.sql ├── 20190913175445-delete-comments.sql ├── 20190131002240-export.sql ├── 20190606000842-reset-hex.sql ├── 20190420181913-sso.sql ├── 20191204173000-sort-method.sql ├── 20180923004309-comment-count-build.sql ├── 20190420231030-sso-tokens.sql ├── 20180922181651-page-attributes.sql ├── 20190505191006-comment-count-decrease.sql ├── 20180923002745-comment-count.sql ├── 20190418210855-configurable-auth.sql ├── new.sh ├── 20200730134007-comment-count-update.sql ├── Makefile └── 20190213033530-email-notifications.sql ├── frontend ├── .gitignore ├── images │ ├── banner.png │ └── 120x120.png ├── sass │ ├── auth.scss │ ├── dashboard.scss │ ├── unsubscribe.scss │ ├── commento-mod-tools.scss │ ├── button.scss │ ├── commento-footer.scss │ ├── commento-logged.scss │ ├── unsubscribe-main.scss │ ├── navbar-main.scss │ ├── commento-oauth.scss │ ├── email-main.scss │ ├── tomorrow.scss │ ├── common-main.scss │ └── commento-login.scss ├── fonts │ ├── source-sans-300-greek.woff2 │ ├── source-sans-300-latin.woff2 │ ├── source-sans-400-greek.woff2 │ ├── source-sans-400-latin.woff2 │ ├── source-sans-700-greek.woff2 │ ├── source-sans-700-latin.woff2 │ ├── source-sans-300-cyrillic.woff2 │ ├── source-sans-300-greek-ext.woff2 │ ├── source-sans-300-latin-ext.woff2 │ ├── source-sans-400-cyrillic.woff2 │ ├── source-sans-400-greek-ext.woff2 │ ├── source-sans-400-latin-ext.woff2 │ ├── source-sans-700-cyrillic.woff2 │ ├── source-sans-700-greek-ext.woff2 │ ├── source-sans-700-latin-ext.woff2 │ ├── source-sans-300-vietnamese.woff2 │ ├── source-sans-400-vietnamese.woff2 │ ├── source-sans-700-vietnamese.woff2 │ ├── source-sans-300-cyrillic-ext.woff2 │ ├── source-sans-400-cyrillic-ext.woff2 │ └── source-sans-700-cyrillic-ext.woff2 ├── Makefile ├── js │ ├── logout.js │ ├── constants.js │ ├── dashboard-installation.js │ ├── auth-common.js │ ├── self.js │ ├── dashboard-export.js │ ├── errors.js │ ├── http.js │ ├── dashboard-setting.js │ ├── forgot.js │ ├── reset.js │ ├── dashboard-general.js │ ├── signup.js │ ├── dashboard-danger.js │ ├── unsubscribe.js │ ├── settings.js │ └── dashboard-import.js ├── logout.html ├── .eslintrc ├── package.json ├── footer.html ├── confirm-email.html ├── forgot.html ├── reset.html └── login.html ├── api ├── constants.go ├── database.go ├── markdown_html.go ├── email_notification.go ├── owner.go ├── commenter_session_new_test.go ├── utils_misc.go ├── utils_sql.go ├── utils_gzip.go ├── page.go ├── utils_logging.go ├── utils_crypto.go ├── utils_logging_test.go ├── email.go ├── domain_view_record.go ├── commenter_session.go ├── cron_views_cleanup.go ├── commenter.go ├── cron_domain_export_cleanup.go ├── page_new.go ├── cron_sso_token.go ├── utils_sanitise_test.go ├── oauth.go ├── commenter_session_update.go ├── comment_domain_path_get.go ├── main.go ├── sigint.go ├── utils_crypto_test.go ├── domain_update_test.go ├── domain_ownership_verify.go ├── email_new.go ├── comment.go ├── comment_ownership_verify.go ├── oauth_github_redirect.go ├── oauth_gitlab_redirect.go ├── oauth_google_redirect.go ├── owner_self.go ├── utils_html.go ├── commenter_self.go ├── akismet.go ├── domain_moderator_new_test.go ├── page_title.go ├── domain_list_test.go ├── markdown.go ├── domain_export_download.go ├── smtp_reset_hex.go ├── smtp_domain_export_error.go ├── database_migrate_email_notifications.go ├── smtp_owner_confirm_hex.go ├── smtp_domain_export.go ├── domain_get_test.go ├── domain_delete_test.go ├── smtp_configure.go ├── comment_domain_path_get_test.go ├── go.mod ├── comment_delete_test.go ├── commenter_session_new.go ├── config_file.go ├── domain_ownership_verify_test.go ├── router.go ├── comment_approve_test.go ├── email_update.go ├── page_get.go ├── comment_statistics.go ├── comment_get.go ├── page_get_test.go ├── oauth_github.go ├── hub.go ├── page_new_test.go ├── owner_new_test.go ├── domain_new_test.go ├── oauth_twitter_redirect.go ├── page_update_test.go ├── oauth_google.go ├── commenter_new_test.go ├── commenter_session_update_test.go ├── comment_ownership_verify_test.go ├── markdown_html_test.go ├── Makefile ├── oauth_gitlab.go ├── utils_http.go ├── domain_moderator.go ├── domain_moderator_delete_test.go ├── owner_login_test.go ├── smtp_templates.go ├── domain.go ├── owner_confirm_hex_test.go ├── oauth_twitter.go ├── commenter_photo.go ├── oauth_google_test.go ├── database_connect.go ├── oauth_sso.go ├── owner_get_test.go ├── owner_confirm_hex.go ├── domain_moderator_delete.go ├── comment_count.go ├── utils_sanitise.go ├── domain_moderator_test.go ├── domain_new.go ├── comment_count_test.go ├── email_moderate.go ├── smtp_configure_test.go ├── domain_get.go ├── domain_sso.go ├── commenter_login_test.go ├── domain_moderator_new.go ├── domain_list.go ├── owner_delete.go ├── email_get.go ├── page_update.go ├── owner_get.go ├── version.go ├── domain_clear.go ├── comment_vote_test.go ├── comment_edit.go ├── owner_login.go ├── reset.go └── commenter_update.go ├── heroku.yml ├── run.sh ├── .editorconfig ├── templates ├── domain-export-error.txt ├── reset-hex.txt ├── confirm-hex.txt ├── domain-export.txt └── Makefile ├── etc ├── linux-systemd │ └── commento.service └── bsd-rc │ └── commento ├── scripts ├── gitlab-ci-build-prescript └── autoserve ├── .circleci └── config.yml ├── docker-compose.yml ├── update_repo.sh ├── LICENSE ├── .gitignore ├── Dockerfile └── Dockerfile.heroku /db/20190218183556-v1.6.1.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .sass-cache 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /api/constants.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | var version string 4 | -------------------------------------------------------------------------------- /db/20180724125115-remove-config.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS config; 2 | -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- 1 | build: 2 | docker: 3 | web: Dockerfile.heroku 4 | -------------------------------------------------------------------------------- /db/20181007231407-v1.1.4.sql: -------------------------------------------------------------------------------- 1 | UPDATE config 2 | SET version = 'v1.2.0'; 3 | -------------------------------------------------------------------------------- /db/20181228114101-v1.4.0.sql: -------------------------------------------------------------------------------- 1 | UPDATE config 2 | SET version = 'v1.4.0'; 3 | -------------------------------------------------------------------------------- /db/20181228114101-v1.4.1.sql: -------------------------------------------------------------------------------- 1 | UPDATE config 2 | SET version = 'v1.4.1'; 3 | -------------------------------------------------------------------------------- /db/20190123002724-v1.4.2.sql: -------------------------------------------------------------------------------- 1 | UPDATE config 2 | SET version = 'v1.4.2'; 3 | -------------------------------------------------------------------------------- /db/20190204180609-v1.5.0.sql: -------------------------------------------------------------------------------- 1 | UPDATE config 2 | SET version = 'v1.5.0'; 3 | -------------------------------------------------------------------------------- /db/20190218173502-v1.6.0.sql: -------------------------------------------------------------------------------- 1 | UPDATE config 2 | SET version = 'v1.6.0'; 3 | -------------------------------------------------------------------------------- /db/20190219001130-v1.6.2.sql: -------------------------------------------------------------------------------- 1 | UPDATE config 2 | SET version = 'v1.6.0'; 3 | -------------------------------------------------------------------------------- /db/20190501201032-v1.7.0.sql: -------------------------------------------------------------------------------- 1 | UPDATE config 2 | SET version = 'v1.7.0'; 3 | -------------------------------------------------------------------------------- /db/20190508222848-reset-count.sql: -------------------------------------------------------------------------------- 1 | UPDATE pages 2 | SET commentCount = commentCount + 1; 3 | -------------------------------------------------------------------------------- /api/database.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | ) 6 | 7 | var db *sql.DB 8 | -------------------------------------------------------------------------------- /frontend/images/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/images/banner.png -------------------------------------------------------------------------------- /db/20180610215858-commenter-password.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE commenters 2 | ADD passwordHash TEXT NOT NULL DEFAULT ''; 3 | -------------------------------------------------------------------------------- /db/20181218183803-sticky-comments.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE pages 2 | ADD stickyCommentHex TEXT NOT NULL DEFAULT 'none'; 3 | -------------------------------------------------------------------------------- /frontend/images/120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/images/120x120.png -------------------------------------------------------------------------------- /frontend/sass/auth.scss: -------------------------------------------------------------------------------- 1 | @import "common-main.scss"; 2 | @import "navbar-main.scss"; 3 | @import "auth-main.scss"; 4 | -------------------------------------------------------------------------------- /frontend/fonts/source-sans-300-greek.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-300-greek.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-300-latin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-300-latin.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-400-greek.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-400-greek.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-400-latin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-400-latin.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-700-greek.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-700-greek.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-700-latin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-700-latin.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-300-cyrillic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-300-cyrillic.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-300-greek-ext.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-300-greek-ext.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-300-latin-ext.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-300-latin-ext.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-400-cyrillic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-400-cyrillic.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-400-greek-ext.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-400-greek-ext.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-400-latin-ext.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-400-latin-ext.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-700-cyrillic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-700-cyrillic.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-700-greek-ext.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-700-greek-ext.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-700-latin-ext.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-700-latin-ext.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-300-vietnamese.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-300-vietnamese.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-400-vietnamese.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-400-vietnamese.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-700-vietnamese.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-700-vietnamese.woff2 -------------------------------------------------------------------------------- /frontend/sass/dashboard.scss: -------------------------------------------------------------------------------- 1 | @import "common-main.scss"; 2 | @import "navbar-main.scss"; 3 | @import "dashboard-main.scss"; 4 | @import "tomorrow.scss"; 5 | -------------------------------------------------------------------------------- /frontend/fonts/source-sans-300-cyrillic-ext.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-300-cyrillic-ext.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-400-cyrillic-ext.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-400-cyrillic-ext.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-700-cyrillic-ext.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/commento-heroku/master/frontend/fonts/source-sans-700-cyrillic-ext.woff2 -------------------------------------------------------------------------------- /frontend/sass/unsubscribe.scss: -------------------------------------------------------------------------------- 1 | @import "common-main.scss"; 2 | @import "navbar-main.scss"; 3 | @import "unsubscribe-main.scss"; 4 | @import "tomorrow.scss"; 5 | -------------------------------------------------------------------------------- /db/20180620083655-session-token-renamme.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE ownerSessions 2 | RENAME COLUMN session TO ownerToken; 3 | 4 | ALTER TABLE commenterSessions 5 | RENAME COLUMN session TO commenterToken 6 | -------------------------------------------------------------------------------- /db/20181007230906-store-version.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS config ( 2 | version TEXT NOT NULL 3 | ); 4 | 5 | INSERT INTO 6 | config (version) 7 | VALUES ('v1.1.3'); 8 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # these vars are not available at buildtime so we need to import them at runtime 4 | export COMMENTO_PORT=$PORT 5 | export COMMENTO_POSTGRES=$DATABASE_URL 6 | 7 | ./commento 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [.*] 2 | charset: utf-8 3 | end_of_line: lf 4 | 5 | [*.go] 6 | indent_style = tab 7 | 8 | [*.js] 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [Makefile] 13 | indent_style = tab -------------------------------------------------------------------------------- /templates/domain-export-error.txt: -------------------------------------------------------------------------------- 1 | You recently requested a data export of your Commento domain {{.Domain}}. An 2 | error was encountered while processing the request. Please contact support to 3 | resolve this issue. 4 | -------------------------------------------------------------------------------- /db/20190122235525-anonymous-moderation-default.sql: -------------------------------------------------------------------------------- 1 | -- Allow the owner to change whether anonymous comments are put into moderation by default. 2 | 3 | ALTER TABLE domains 4 | ADD COLUMN moderateAllAnonymous BOOLEAN DEFAULT true; 5 | -------------------------------------------------------------------------------- /db/20190913175445-delete-comments.sql: -------------------------------------------------------------------------------- 1 | DROP TRIGGER IF EXISTS commentsDeleteTrigger ON comments; 2 | 3 | DROP FUNCTION IF EXISTS commentsDeleteTriggerFunction(); 4 | 5 | ALTER TABLE comments 6 | ADD deleted BOOLEAN NOT NULL DEFAULT false; 7 | -------------------------------------------------------------------------------- /db/20190131002240-export.sql: -------------------------------------------------------------------------------- 1 | -- add export feature 2 | 3 | CREATE TABLE IF NOT EXISTS exports ( 4 | exportHex TEXT NOT NULL UNIQUE PRIMARY KEY, 5 | binData BYTEA NOT NULL, 6 | domain TEXT NOT NULL, 7 | creationDate TIMESTAMP NOT NULL 8 | ); 9 | -------------------------------------------------------------------------------- /db/20190606000842-reset-hex.sql: -------------------------------------------------------------------------------- 1 | -- Create the resetHexes table 2 | 3 | ALTER TABLE ownerResetHexes RENAME TO resetHexes; 4 | 5 | ALTER TABLE resetHexes RENAME ownerHex TO hex; 6 | 7 | ALTER TABLE resetHexes 8 | ADD entity TEXT NOT NULL DEFAULT 'owner'; 9 | -------------------------------------------------------------------------------- /frontend/Makefile: -------------------------------------------------------------------------------- 1 | BUILD_DIR = build 2 | GULP = node_modules/.bin/gulp 3 | 4 | devel: 5 | yarn install 6 | $(GULP) devel 7 | 8 | prod: 9 | yarn install 10 | $(GULP) prod 11 | 12 | clean: 13 | -rm -rf $(BUILD_DIR); 14 | -------------------------------------------------------------------------------- /frontend/js/logout.js: -------------------------------------------------------------------------------- 1 | (function (global, document) { 2 | "use strict"; 3 | 4 | global.logout = function() { 5 | global.cookieDelete("commentoOwnerToken"); 6 | document.location = global.origin + "/login"; 7 | } 8 | 9 | } (window.commento, document)); 10 | -------------------------------------------------------------------------------- /frontend/logout.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /api/markdown_html.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/russross/blackfriday" 5 | ) 6 | 7 | func markdownToHtml(markdown string) string { 8 | unsafe := blackfriday.Markdown([]byte(markdown), renderer, extensions) 9 | return string(policy.SanitizeBytes(unsafe)) 10 | } 11 | -------------------------------------------------------------------------------- /db/20190420181913-sso.sql: -------------------------------------------------------------------------------- 1 | -- Single Sign-On (SSO) 2 | 3 | ALTER TABLE domains 4 | ADD ssoProvider BOOLEAN NOT NULL DEFAULT false; 5 | 6 | ALTER TABLE domains 7 | ADD ssoSecret TEXT NOT NULL DEFAULT ''; 8 | 9 | ALTER TABLE domains 10 | ADD ssoUrl TEXT NOT NULL DEFAULT ''; 11 | -------------------------------------------------------------------------------- /templates/reset-hex.txt: -------------------------------------------------------------------------------- 1 | Hi, 2 | 3 | Someone (probably you) recently initiated the procedure to reset your Commento account password. To do this, use the link below: 4 | 5 | {{.Origin}}/reset?hex={{.ResetHex}} 6 | 7 | If you did not initiate this request, you can safely ignore this email. 8 | -------------------------------------------------------------------------------- /api/email_notification.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import () 4 | 5 | type emailNotification struct { 6 | Email string 7 | CommenterName string 8 | Domain string 9 | Path string 10 | Title string 11 | CommentHex string 12 | Kind string 13 | } 14 | -------------------------------------------------------------------------------- /db/20191204173000-sort-method.sql: -------------------------------------------------------------------------------- 1 | -- Default sort policy for each domain 2 | 3 | CREATE TYPE sortPolicy AS ENUM ( 4 | 'score-desc', 5 | 'creationdate-desc', 6 | 'creationdate-asc' 7 | ); 8 | 9 | ALTER TABLE domains 10 | ADD defaultSortPolicy sortPolicy NOT NULL DEFAULT 'score-desc'; 11 | -------------------------------------------------------------------------------- /templates/confirm-hex.txt: -------------------------------------------------------------------------------- 1 | Hi, 2 | 3 | You recently registered a new Commento account with this email address. If you wish to complete registration, use the link below: 4 | 5 | {{.Origin}}/api/owner/confirm-hex?confirmHex={{.ConfirmHex}} 6 | 7 | If you did not do initiate this, you can ignore this email. 8 | -------------------------------------------------------------------------------- /db/20180923004309-comment-count-build.sql: -------------------------------------------------------------------------------- 1 | -- Build the comments count column 2 | 3 | UPDATE pages 4 | SET commentCount = subquery.commentCount 5 | FROM ( 6 | SELECT COUNT(commentHex) as commentCount 7 | FROM comments 8 | WHERE state = 'approved' 9 | GROUP BY (domain, path) 10 | ) as subquery; 11 | -------------------------------------------------------------------------------- /frontend/js/constants.js: -------------------------------------------------------------------------------- 1 | (function (global, document) { 2 | "use strict"; 3 | 4 | (document); 5 | 6 | if (window.commento === undefined) { 7 | window.commento = {}; 8 | } 9 | 10 | window.commento.origin = "[[[.Origin]]]"; 11 | window.commento.cdn = "[[[.CdnPrefix]]]"; 12 | 13 | } (window, document)); 14 | -------------------------------------------------------------------------------- /api/owner.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type owner struct { 8 | OwnerHex string `json:"ownerHex"` 9 | Email string `json:"email"` 10 | Name string `json:"name"` 11 | ConfirmedEmail bool `json:"confirmedEmail"` 12 | JoinDate time.Time `json:"joinDate"` 13 | } 14 | -------------------------------------------------------------------------------- /api/commenter_session_new_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCommenterTokenNewBasics(t *testing.T) { 8 | failTestOnError(t, setupTestEnv()) 9 | 10 | if _, err := commenterTokenNew(); err != nil { 11 | t.Errorf("unexpected error creating new commenterToken: %v", err) 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /api/utils_misc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func concat(a bytes.Buffer, b bytes.Buffer) []byte { 10 | return append(a.Bytes(), b.Bytes()...) 11 | } 12 | 13 | func exitIfError(err error) { 14 | if err != nil { 15 | fmt.Printf("fatal error: %v\n", err) 16 | os.Exit(1) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /api/utils_sql.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import () 4 | 5 | // scanner is a database/sql abstraction interface that can be used with both 6 | // *sql.Row and *sql.Rows. 7 | type sqlScanner interface { 8 | // Scan copies columns from the underlying query row(s) to the values 9 | // pointed to by dest. 10 | Scan(dest ...interface{}) error 11 | } 12 | -------------------------------------------------------------------------------- /api/utils_gzip.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | ) 7 | 8 | func gzipStatic(b []byte) ([]byte, error) { 9 | var buf bytes.Buffer 10 | g := gzip.NewWriter(&buf) 11 | if _, err := g.Write(b); err != nil { 12 | g.Close() 13 | return []byte{}, err 14 | } 15 | 16 | g.Close() 17 | return buf.Bytes(), nil 18 | } 19 | -------------------------------------------------------------------------------- /templates/domain-export.txt: -------------------------------------------------------------------------------- 1 | You recently requested a data export of your Commento domain {{.Domain}}. You 2 | can download a GZipped archive of a JSON export of all the comments and 3 | commenters associated with the domain here: 4 | 5 | {{.Origin}}/api/domain/export/download?exportHex={{.ExportHex}} 6 | 7 | The archive will be available for download for 7 days. 8 | -------------------------------------------------------------------------------- /api/page.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import () 4 | 5 | type page struct { 6 | Domain string `json:"domain"` 7 | Path string `json:"path"` 8 | IsLocked bool `json:"isLocked"` 9 | CommentCount int `json:"commentCount"` 10 | StickyCommentHex string `json:"stickyCommentHex"` 11 | Title string `json:"title"` 12 | } 13 | -------------------------------------------------------------------------------- /db/20190420231030-sso-tokens.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS ssoTokens ( 2 | token TEXT NOT NULL UNIQUE PRIMARY KEY , 3 | domain TEXT NOT NULL , 4 | commenterToken TEXT NOT NULL , 5 | creationDate TIMESTAMP NOT NULL 6 | ); 7 | -------------------------------------------------------------------------------- /api/utils_logging.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/op/go-logging" 5 | ) 6 | 7 | var logger *logging.Logger 8 | 9 | func loggerCreate() error { 10 | format := logging.MustStringFormatter("[%{level}] %{shortfile} %{shortfunc}(): %{message}") 11 | logging.SetFormatter(format) 12 | logger = logging.MustGetLogger("commento") 13 | 14 | return nil 15 | } 16 | -------------------------------------------------------------------------------- /api/utils_crypto.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/hex" 6 | ) 7 | 8 | func randomHex(n int) (string, error) { 9 | b := make([]byte, n) 10 | if _, err := rand.Read(b); err != nil { 11 | logger.Errorf("cannot create %d-byte long random hex: %v\n", n, err) 12 | return "", errorInternal 13 | } 14 | 15 | return hex.EncodeToString(b), nil 16 | } 17 | -------------------------------------------------------------------------------- /db/20180922181651-page-attributes.sql: -------------------------------------------------------------------------------- 1 | -- Introduces page attributes 2 | 3 | CREATE TABLE IF NOT EXISTS pages ( 4 | domain TEXT NOT NULL , 5 | path TEXT NOT NULL , 6 | isLocked BOOLEAN NOT NULL DEFAULT false 7 | ); 8 | 9 | CREATE UNIQUE INDEX pagesUniqueIndex ON pages(domain, path); 10 | -------------------------------------------------------------------------------- /api/utils_logging_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestLoggerCreateBasics(t *testing.T) { 8 | logger = nil 9 | 10 | if err := loggerCreate(); err != nil { 11 | t.Errorf("unexpected error creating logger: %v", err) 12 | return 13 | } 14 | 15 | if logger == nil { 16 | t.Errorf("logger null after loggerCreate()") 17 | return 18 | } 19 | 20 | logger.Debugf("test message please ignore") 21 | } 22 | -------------------------------------------------------------------------------- /api/email.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type email struct { 8 | Email string `json:"email"` 9 | UnsubscribeSecretHex string `json:"unsubscribeSecretHex"` 10 | LastEmailNotificationDate time.Time `json:"lastEmailNotificationDate"` 11 | SendReplyNotifications bool `json:"sendReplyNotifications"` 12 | SendModeratorNotifications bool `json:"sendModeratorNotifications"` 13 | } 14 | -------------------------------------------------------------------------------- /api/domain_view_record.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //import ( 4 | // "time" 5 | //) 6 | 7 | func domainViewRecord(domain string, commenterHex string) { 8 | // statement := ` 9 | // INSERT INTO 10 | // views (domain, commenterHex, viewDate) 11 | // VALUES ($1, $2, $3 ); 12 | //` 13 | // _, err := db.Exec(statement, domain, commenterHex, time.Now().UTC()) 14 | // if err != nil { 15 | // logger.Warningf("cannot insert views: %v", err) 16 | // } 17 | } 18 | -------------------------------------------------------------------------------- /api/commenter_session.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // A session is a 3-field entry of a token, a hex, and a creation date. Do 8 | // not confuse session and token; the token is just an identifying string, 9 | // while the session contains more information. 10 | type commenterSession struct { 11 | CommenterToken string `json:"commenterToken"` 12 | CommenterHex string `json:"commenterHex"` 13 | CreationDate time.Time `json:"creationDate"` 14 | } 15 | -------------------------------------------------------------------------------- /etc/linux-systemd/commento.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Commento daemon service 3 | After=network.target postgresql.service 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart=/usr/bin/commento 8 | Environment=COMMENTO_ORIGIN=https://commento.example.com 9 | Environment=COMMENTO_PORT=8080 10 | Environment=COMMENTO_POSTGRES=postgres://commento:commento@db:5432/commento?sslmode=disable 11 | Environment=COMMENTO_STATIC=/usr/share/commento 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /api/cron_views_cleanup.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | func viewsCleanupBegin() error { 8 | go func() { 9 | for { 10 | statement := ` 11 | DELETE FROM views 12 | WHERE viewDate < $1; 13 | ` 14 | _, err := db.Exec(statement, time.Now().UTC().AddDate(0, 0, -45)) 15 | if err != nil { 16 | logger.Errorf("error cleaning up views: %v", err) 17 | return 18 | } 19 | 20 | time.Sleep(24 * time.Hour) 21 | } 22 | }() 23 | 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /api/commenter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type commenter struct { 8 | CommenterHex string `json:"commenterHex,omitempty"` 9 | Email string `json:"email,omitempty"` 10 | Name string `json:"name"` 11 | Link string `json:"link"` 12 | Photo string `json:"photo"` 13 | Provider string `json:"provider,omitempty"` 14 | JoinDate time.Time `json:"joinDate,omitempty"` 15 | IsModerator bool `json:"isModerator"` 16 | } 17 | -------------------------------------------------------------------------------- /db/20190505191006-comment-count-decrease.sql: -------------------------------------------------------------------------------- 1 | -- This trigger is called every time a comment is deleted, so the comment count for the page where the comment belong is updated 2 | CREATE OR REPLACE FUNCTION commentsDeleteTriggerFunction() RETURNS TRIGGER AS $trigger$ 3 | BEGIN 4 | UPDATE pages 5 | SET commentCount = commentCount - 1 6 | WHERE domain = old.domain AND path = old.path; 7 | 8 | DELETE FROM comments 9 | WHERE parentHex = old.commentHex; 10 | 11 | RETURN NEW; 12 | END; 13 | $trigger$ LANGUAGE plpgsql; 14 | -------------------------------------------------------------------------------- /api/cron_domain_export_cleanup.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | func domainExportCleanupBegin() error { 8 | go func() { 9 | for { 10 | statement := ` 11 | DELETE FROM exports 12 | WHERE creationDate < $1; 13 | ` 14 | _, err := db.Exec(statement, time.Now().UTC().AddDate(0, 0, -7)) 15 | if err != nil { 16 | logger.Errorf("error cleaning up export rows: %v", err) 17 | return 18 | } 19 | 20 | time.Sleep(2 * time.Hour) 21 | } 22 | }() 23 | 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /api/page_new.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import () 4 | 5 | func pageNew(domain string, path string) error { 6 | // path can be empty 7 | if domain == "" { 8 | return errorMissingField 9 | } 10 | 11 | statement := ` 12 | INSERT INTO 13 | pages (domain, path) 14 | VALUES ($1, $2 ) 15 | ON CONFLICT DO NOTHING; 16 | ` 17 | _, err := db.Exec(statement, domain, path) 18 | if err != nil { 19 | logger.Errorf("error inserting new page: %v", err) 20 | return errorInternal 21 | } 22 | 23 | return nil 24 | } 25 | -------------------------------------------------------------------------------- /db/20180923002745-comment-count.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE pages 2 | ADD commentCount INTEGER NOT NULL DEFAULT 0; 3 | 4 | CREATE OR REPLACE FUNCTION commentsInsertTriggerFunction() RETURNS TRIGGER AS $trigger$ 5 | BEGIN 6 | UPDATE pages 7 | SET commentCount = commentCount + 1 8 | WHERE domain = new.domain AND path = new.path; 9 | 10 | RETURN NEW; 11 | END; 12 | $trigger$ LANGUAGE plpgsql; 13 | 14 | CREATE TRIGGER commentsInsertTrigger AFTER INSERT ON comments 15 | FOR EACH ROW EXECUTE PROCEDURE commentsInsertTriggerFunction(); 16 | -------------------------------------------------------------------------------- /db/20190418210855-configurable-auth.sql: -------------------------------------------------------------------------------- 1 | -- Make all login providers optional (but enabled by default) 2 | 3 | ALTER TABLE domains 4 | ADD commentoProvider BOOLEAN NOT NULL DEFAULT true; 5 | 6 | ALTER TABLE domains 7 | ADD googleProvider BOOLEAN NOT NULL DEFAULT true; 8 | 9 | ALTER TABLE domains 10 | ADD twitterProvider BOOLEAN NOT NULL DEFAULT true; 11 | 12 | ALTER TABLE domains 13 | ADD githubProvider BOOLEAN NOT NULL DEFAULT true; 14 | 15 | ALTER TABLE domains 16 | ADD gitlabProvider BOOLEAN NOT NULL DEFAULT true; 17 | -------------------------------------------------------------------------------- /api/cron_sso_token.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | func ssoTokenCleanupBegin() error { 8 | go func() { 9 | for { 10 | statement := ` 11 | DELETE FROM ssoTokens 12 | WHERE creationDate < $1; 13 | ` 14 | _, err := db.Exec(statement, time.Now().UTC().Add(time.Duration(-10)*time.Minute)) 15 | if err != nil { 16 | logger.Errorf("error cleaning up export rows: %v", err) 17 | return 18 | } 19 | 20 | time.Sleep(10 * time.Minute) 21 | } 22 | }() 23 | 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /scripts/gitlab-ci-build-prescript: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mkdir -p /go/src /go/bin /go/pkg 4 | ln -s $CI_PROJECT_DIR /go/src/$CI_PROJECT_NAME 5 | 6 | apt update 7 | apt install -y curl gnupg git make golang python 8 | export GOPATH=/go 9 | export PATH=$PATH:/go/bin 10 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 11 | 12 | curl -sL https://deb.nodesource.com/setup_10.x | bash - 13 | apt install -y nodejs 14 | npm install -g yarn@1.10.0 15 | 16 | apt install -y python python-pip 17 | pip install awscli 18 | -------------------------------------------------------------------------------- /api/utils_sanitise_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestEmailStripBasics(t *testing.T) { 8 | tests := map[string]string{ 9 | "test@example.com": "test@example.com", 10 | "test+strip@example.com": "test@example.com", 11 | "test+strip+strip2@example.com": "test@example.com", 12 | } 13 | 14 | for in, out := range tests { 15 | if emailStrip(in) != out { 16 | t.Errorf("for in=%s expected out=%s got out=%s", in, out, emailStrip(in)) 17 | return 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /api/oauth.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import () 4 | 5 | var googleConfigured bool 6 | var twitterConfigured bool 7 | var githubConfigured bool 8 | var gitlabConfigured bool 9 | 10 | func oauthConfigure() error { 11 | if err := googleOauthConfigure(); err != nil { 12 | return err 13 | } 14 | 15 | if err := twitterOauthConfigure(); err != nil { 16 | return err 17 | } 18 | 19 | if err := githubOauthConfigure(); err != nil { 20 | return err 21 | } 22 | 23 | if err := gitlabOauthConfigure(); err != nil { 24 | return err 25 | } 26 | 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/golang:1.9 6 | 7 | working_directory: /go/src/github.com/{{ORG_NAME}}/{{REPO_NAME}} 8 | triggers: 9 | - schedule: 10 | cron: "0 0 * * *" 11 | filters: 12 | branches: 13 | only: 14 | - autoupdate 15 | steps: 16 | - checkout 17 | - add_ssh_keys: 18 | fingerprints: 19 | - "3c:e1:34:34:88:c6:c4:e0:b3:72:e5:dd:7d:bd:ef:d9" 20 | - run: bash update_repo.sh -xe 21 | 22 | -------------------------------------------------------------------------------- /api/commenter_session_update.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import () 4 | 5 | func commenterSessionUpdate(commenterToken string, commenterHex string) error { 6 | if commenterToken == "" || commenterHex == "" { 7 | return errorMissingField 8 | } 9 | 10 | statement := ` 11 | UPDATE commenterSessions 12 | SET commenterHex = $2 13 | WHERE commenterToken = $1; 14 | ` 15 | _, err := db.Exec(statement, commenterToken, commenterHex) 16 | if err != nil { 17 | logger.Errorf("error updating commenterHex: %v", err) 18 | return errorInternal 19 | } 20 | 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /api/comment_domain_path_get.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import () 4 | 5 | func commentDomainPathGet(commentHex string) (string, string, error) { 6 | if commentHex == "" { 7 | return "", "", errorMissingField 8 | } 9 | 10 | statement := ` 11 | SELECT domain, path 12 | FROM comments 13 | WHERE commentHex = $1; 14 | ` 15 | row := db.QueryRow(statement, commentHex) 16 | 17 | var domain string 18 | var path string 19 | var err error 20 | if err = row.Scan(&domain, &path); err != nil { 21 | return "", "", errorNoSuchDomain 22 | } 23 | 24 | return domain, path, nil 25 | } 26 | -------------------------------------------------------------------------------- /frontend/sass/commento-mod-tools.scss: -------------------------------------------------------------------------------- 1 | @import "colors-main.scss"; 2 | 3 | .commento-mod-tools { 4 | margin-bottom: 16px; 5 | 6 | button { 7 | text-transform: uppercase; 8 | color: $gray-7; 9 | font-size: 12px; 10 | font-weight: 700; 11 | cursor: pointer; 12 | margin-left: 12px; 13 | background: none; 14 | border: none; 15 | display: inline; 16 | } 17 | } 18 | 19 | .commento-mod-tools::before { 20 | content: "Moderator Tools"; 21 | text-transform: uppercase; 22 | color: $indigo-8; 23 | font-size: 12px; 24 | font-weight: 700; 25 | } 26 | -------------------------------------------------------------------------------- /api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | exitIfError(loggerCreate()) 5 | exitIfError(versionPrint()) 6 | exitIfError(configParse()) 7 | exitIfError(dbConnect(5)) 8 | exitIfError(migrate()) 9 | exitIfError(smtpConfigure()) 10 | exitIfError(smtpTemplatesLoad()) 11 | exitIfError(oauthConfigure()) 12 | exitIfError(markdownRendererCreate()) 13 | exitIfError(sigintCleanupSetup()) 14 | exitIfError(versionCheckStart()) 15 | exitIfError(domainExportCleanupBegin()) 16 | exitIfError(viewsCleanupBegin()) 17 | exitIfError(ssoTokenCleanupBegin()) 18 | 19 | exitIfError(routesServe()) 20 | } 21 | -------------------------------------------------------------------------------- /api/sigint.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | "syscall" 7 | ) 8 | 9 | func sigintCleanup() int { 10 | if db != nil { 11 | err := db.Close() 12 | if err == nil { 13 | logger.Errorf("cannot close database connection: %v", err) 14 | return 1 15 | } 16 | } 17 | 18 | return 0 19 | } 20 | 21 | func sigintCleanupSetup() error { 22 | logger.Infof("setting up SIGINT cleanup") 23 | 24 | c := make(chan os.Signal) 25 | signal.Notify(c, os.Interrupt, syscall.SIGINT) 26 | go func() { 27 | <-c 28 | os.Exit(sigintCleanup()) 29 | }() 30 | 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /api/utils_crypto_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestRandomHexBasics(t *testing.T) { 8 | hex1, err := randomHex(32) 9 | if err != nil { 10 | t.Errorf("unexpected error creating hex: %v", err) 11 | return 12 | } 13 | 14 | if hex1 == "" { 15 | t.Errorf("randomly generated hex empty") 16 | return 17 | } 18 | 19 | hex2, err := randomHex(32) 20 | if err != nil { 21 | t.Errorf("unexpected error creating hex: %v", err) 22 | return 23 | } 24 | 25 | if hex1 == hex2 { 26 | t.Errorf("two randomly generated hexes found to be the same: '%s'", hex1) 27 | return 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /api/domain_update_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDomainUpdateBasics(t *testing.T) { 8 | failTestOnError(t, setupTestEnv()) 9 | 10 | domainNew("temp-owner-hex", "Example", "example.com") 11 | 12 | d, _ := domainList("temp-owner-hex") 13 | 14 | d[0].Name = "Example2" 15 | 16 | if err := domainUpdate(d[0]); err != nil { 17 | t.Errorf("unexpected error updating domain: %v", err) 18 | return 19 | } 20 | 21 | d, _ = domainList("temp-owner-hex") 22 | 23 | if d[0].Name != "Example2" { 24 | t.Errorf("expected name=Example2 got name=%s", d[0].Name) 25 | return 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /api/domain_ownership_verify.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import () 4 | 5 | func domainOwnershipVerify(ownerHex string, domain string) (bool, error) { 6 | if ownerHex == "" || domain == "" { 7 | return false, errorMissingField 8 | } 9 | 10 | statement := ` 11 | SELECT EXISTS ( 12 | SELECT 1 13 | FROM domains 14 | WHERE ownerHex=$1 AND domain=$2 15 | ); 16 | ` 17 | row := db.QueryRow(statement, ownerHex, domain) 18 | 19 | var exists bool 20 | if err := row.Scan(&exists); err != nil { 21 | logger.Errorf("cannot query if domain owner: %v", err) 22 | return false, errorInternal 23 | } 24 | 25 | return exists, nil 26 | } 27 | -------------------------------------------------------------------------------- /frontend/js/dashboard-installation.js: -------------------------------------------------------------------------------- 1 | (function (global, document) { 2 | "use strict"; 3 | 4 | (document); 5 | 6 | // Opens the installation view. 7 | global.installationOpen = function() { 8 | var html = "" + 9 | " 5 | 6 | 7 |