Move away from dbmate, use PolySchema

This commit is contained in:
Jamie Curnow committed 2026-09-28 11:31:27 +10:00
1 parent 52399c42f1
commit 2a14f3f84c
99 files changed
+1583 -1691

No files matched your search

+150 -139
View File
@@ -1,4 +1,17 @@
---
version: "2"
run:
go: "1.24"
# If set, we pass it to "go list -mod={option}". From "go help modules":
# If invoked with -mod=readonly, the go command is disallowed from the
# implicit automatic updating of go.mod described above. Instead, it
# fails when any changes to go.mod are needed. This setting is most
# useful to check that go.mod does not need updates, such as in a
# continuous integration and testing system.
#
# Allowed values: readonly|vendor|mod
# Default: ""
modules-download-mode: readonly
linters:
enable:
# Prevents against memory leaks in production caused by not closing
@@ -7,42 +20,19 @@ linters:
# Detects cloned code. DRY is good programming practice. Can cause issues
# with testing code where simplicity is preferred over duplication.
# Disabled for test code.
# - dupl
# Detects unchecked errors in go programs. These unchecked errors can be
# critical bugs in some cases.
- errcheck
# Simplifies go code.
- gosimple
# Controls Go package import order and makes it always deterministic.
- gci
# Reports suspicious constructs, maintained by goteam. e.g. Printf unused
# params not caught at compile time.
- govet
# Detect security issues with gocode. Use of secrets in code or obsolete
# security algorithms. It's imaged heuristic methods are used in finding
# problems. If issues with rules are found particular rules can be disabled
# as required. Could possibility cause issues with testing.
# Disabled for test code.
- gosec
# Detect repeated strings that could be replaced by a constant
- dupl
- goconst
# Misc linters missing from other projects. Grouped into 3 categories
# diagnostics, style and performance
- gocritic
# Limits code cyclomatic complexity
- gocyclo
# Detects if code needs to be gofmt'd
- gofmt
# Detects unused go package imports
- goimports
# Detects style mistakes not correctness. Golint is meant to carry out the
# stylistic conventions put forth in Effective Go and CodeReviewComments.
# golint has false positives and false negatives and can be tweaked.
- revive
# Detects ineffectual assignments in code
- ineffassign
# Reports long lines
# - lll
# Detect security issues with gocode. Use of secrets in code or obsolete
# security algorithms. It's imaged heuristic methods are used in finding
# problems. If issues with rules are found particular rules can be disabled
# as required. Could possibility cause issues with testing.
# Disabled for test code.
- gosec
# Detect commonly misspelled english words in comments
- misspell
# Detect naked returns on non-trivial functions, and conform with
@@ -50,117 +40,138 @@ linters:
- nakedret
# Detect slice allocations that can be preallocated
- prealloc
# Misc collection of static analysis tools
- staticcheck
# Detects unused struct fields
# - structcheck
# Parses and typechecks the code like the go compiler
- typecheck
# Detects unused constants, variables, functions and types
- unused
# Detects style mistakes not correctness. Golint is meant to carry out the
# stylistic conventions put forth in Effective Go and CodeReviewComments.
# golint has false positives and false negatives and can be tweaked.
- revive
# Remove unnecessary type conversions
- unconvert
# Remove unnecessary(unused) function parameters
- unparam
linters-settings:
errcheck:
exclude-functions:
- fmt.Fprint
- fmt.Fprintf
gci:
sections:
- standard # Standard section: captures all standard packages.
- localmodule # Local module section: contains all local packages.
# - prefix(gogs.jc21.com) # Prefixed gerrit.lan packages (jumgo).
- default # Everything else (github.com, golang.org, etc).
- blank # Blank section: contains all blank imports.
custom-order: true
goconst:
# minimal length of string constant
# default: 3
min-len: 2
# minimum number of occurrences of string constant
# default: 3
min-occurences: 2
revive:
enable-all-rules: true
settings:
# Detects unchecked errors in go programs. These unchecked errors can be
# critical bugs in some cases.
errcheck:
exclude-functions:
- fmt.Fprint
- fmt.Fprintf
# Detect repeated strings that could be replaced by a constant
goconst:
# minimal length of string constant
# default: 3
min-len: 2
gosec:
excludes:
- G201 # Allow formatted string for queries
revive:
enable-all-rules: true
rules:
# handled by goconst
- name: add-constant
disabled: true
# cant limit this arbitrarily
- name: argument-limit
disabled: true
# handled by gocyclo
- name: cognitive-complexity
disabled: true
# false positive for Exported vs non-exported functions of the same name
- name: confusing-naming
disabled: true
# false positives for "" - which is the nil value of a string (also 0)
- name: confusing-results
disabled: true
# handled by gocyclo
- name: cyclomatic
disabled: true
# some cli programs want to exit on error
- name: deep-exit
disabled: true
# have comments on exported functions but not on vars/types/constants
- name: exported
arguments:
- disableChecksOnConstants
- disableChecksOnTypes
- disableChecksOnVariables
# false positives on bool params
- name: flag-parameter
disabled: true
# extreme verticalization can happen
- name: function-length
disabled: true
# return as many as you like
- name: function-result-limit
disabled: true
# can false positive for non-getters
- name: get-return
disabled: true
# only allows lowercase names
- name: import-alias-naming
disabled: true
# handled by lll
- name: line-length-limit
disabled: true
# handled by gocyclo
- name: max-control-nesting
disabled: true
# don't want to arbitrarily limit this
# many places have specific model.go files to contain all structs
- name: max-public-structs
disabled: true
# disable package-comments
- name: package-comments
disabled: true
# allow ignoring assertion result
- name: unchecked-type-assertion
arguments:
- acceptIgnoredAssertionResult: true
# this is handled by errcheck
- name: unhandled-error
disabled: true
- name: enforce-switch-style
disabled: true
- name: package-naming
disabled: true
exclusions:
generated: lax
rules:
- name: unchecked-type-assertion
disabled: true
# handled by goconst
- name: add-constant
disabled: true
# cant limit this arbitrarily
- name: argument-limit
disabled: true
# handled by gocyclo
- name: cognitive-complexity
disabled: true
# false positive for Exported vs non-exported functions of the same name
- name: confusing-naming
disabled: true
# false positives for "" - which is the nil value of a string (also 0)
- name: confusing-results
disabled: true
# handled by gocyclo
- name: cyclomatic
disabled: true
# have comments on exported functions but not on vars/types/constants
- name: exported
arguments:
- "disableChecksOnConstants"
- "disableChecksOnTypes"
- "disableChecksOnVariables"
# false positives on bool params
- name: flag-parameter
disabled: true
# extreme verticalization can happen
- name: function-length
disabled: true
# can false positive for non-getters
- name: get-return
disabled: true
# only allows lowercase names
- name: import-alias-naming
disabled: true
# handled by lll
- name: line-length-limit
disabled: true
# don't want to arbitrarily limit this
# many places have specific model.go files to contain all structs
- name: max-public-structs
disabled: true
# disable package-comments
- name: package-comments
disabled: true
# this is handled by errcheck
- name: unhandled-error
disabled: true
- name: function-result-limit
disabled: true
issues:
# Maximum count of issues with the same text. Set to 0 to disable. Default
# is 3. We have chosen an arbitrary value that works based on practical usage.
max-same: 20
# See cmdline flag documentation for more info about default excludes
# --exclude-use-default. Nothing is excluded by default
exclude-use-default: false
# Excluding configuration per-path, per-linter, per-text and per-source
exclude-rules:
# Exclude some linters from running on tests files.
# TODO: Add examples why this is good
- path: _test\.go
linters:
# Tests should be simple? Add example why this is good?
- gocyclo
# Error checking adds verbosity and complexity for minimal value
- errcheck
# Table test encourage duplication in defining the table tests.
- dupl
# Hard coded example tokens, SQL injection and other bad practices may
# want to be tested
- gosec
# Test data can long
# - lll
run:
go: '1.23'
- linters:
# Table test encourage duplication in defining the table tests.
- dupl
# Test fixtures read better as literals than as constants
- goconst
# Error checking adds verbosity and complexity for minimal value
- errcheck
# Tests should be simple? Add example why this is good?
- gocyclo
# Hard coded example tokens, SQL injection and other bad practices may
# want to be tested
- gosec
# Test data can long
- lll
path: _test\.go
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
# Controls Go package import order and makes it always deterministic.
- gci
# Detects if code needs to be gofmt'd
- gofmt
settings:
gci:
sections:
- standard # Standard section: captures all standard packages
- localmodule # Local module section contains all local packages
- prefix(gerrit.lan) # Prefixed gerrit.lan packages (jumgo)
- default # Everything else (github.com, golang.org, etc)
- blank # Blank section: contains all blank imports
custom-order: true
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
+5
View File
@@ -12,6 +12,11 @@ var APIDocFiles embed.FS
//go:embed assets
var Assets embed.FS
// MigrationFiles is for database schema migrations
//
//go:embed migrations
var MigrationFiles embed.FS
// NginxFiles hold nginx config templates
//
//go:embed nginx
@@ -0,0 +1,224 @@
# The application's initial schema.
#
# Columns are nullable unless marked otherwise. Two columns use a bounded
# string rather than text because MySQL can't make a TEXT column a primary
# key (capability.name) or give it a literal default (auth.type). They're
# still TEXT on SQLite.
operations:
- createTable:
name: access_list
columns:
- { name: id, type: bigint, primaryKey: true, autoIncrement: true }
- { name: created_at, type: bigint }
- { name: updated_at, type: bigint }
- { name: is_deleted, type: bigint }
- { name: user_id, type: bigint }
- { name: name, type: text }
- { name: meta, type: text }
- createTable:
name: audit_log
columns:
- { name: id, type: bigint, primaryKey: true, autoIncrement: true }
- { name: created_at, type: bigint }
- { name: updated_at, type: bigint }
- { name: is_deleted, type: bigint }
- { name: user_id, type: bigint }
- { name: object_type, type: text }
- { name: object_id, type: bigint }
- { name: action, type: text }
- { name: meta, type: text }
- createTable:
name: auth
columns:
- { name: id, type: bigint, primaryKey: true, autoIncrement: true }
- { name: created_at, type: bigint }
- { name: updated_at, type: bigint }
- { name: is_deleted, type: bigint }
- { name: user_id, type: bigint }
- { name: type, type: string, length: 191, default: local }
- { name: identity, type: text }
- { name: secret, type: text }
- createTable:
name: capability
columns:
- { name: name, type: string, length: 191, primaryKey: true }
- createTable:
name: certificate
columns:
- { name: id, type: bigint, primaryKey: true, autoIncrement: true }
- { name: created_at, type: bigint }
- { name: updated_at, type: bigint }
- { name: is_deleted, type: bigint }
- { name: user_id, type: bigint }
- { name: type, type: text }
- { name: certificate_authority_id, type: bigint }
- { name: dns_provider_id, type: bigint }
- { name: name, type: text }
- { name: domain_names, type: text }
- { name: expires_on, type: bigint }
- { name: status, type: text }
- { name: error_message, type: text }
- { name: meta, type: text }
- { name: is_ecc, type: boolean }
- createTable:
name: certificate_authority
columns:
- { name: id, type: bigint, primaryKey: true, autoIncrement: true }
- { name: created_at, type: bigint }
- { name: updated_at, type: bigint }
- { name: is_deleted, type: bigint }
- { name: name, type: text }
- { name: acmesh_server, type: text }
- { name: ca_bundle, type: text }
- { name: max_domains, type: bigint }
- { name: is_wildcard_supported, type: boolean }
- { name: is_readonly, type: boolean }
- createTable:
name: dns_provider
columns:
- { name: id, type: bigint, primaryKey: true, autoIncrement: true }
- { name: created_at, type: bigint }
- { name: updated_at, type: bigint }
- { name: is_deleted, type: bigint }
- { name: user_id, type: bigint }
- { name: name, type: text }
- { name: acmesh_name, type: text }
- { name: dns_sleep, type: bigint }
- { name: meta, type: text }
- createTable:
name: host
columns:
- { name: id, type: bigint, primaryKey: true, autoIncrement: true }
- { name: created_at, type: bigint }
- { name: updated_at, type: bigint }
- { name: is_deleted, type: bigint }
- { name: user_id, type: bigint }
- { name: type, type: text }
- { name: nginx_template_id, type: bigint }
- { name: listen_interface, type: text }
- { name: domain_names, type: text }
- { name: upstream_id, type: bigint }
- { name: proxy_scheme, type: text }
- { name: proxy_host, type: text }
- { name: proxy_port, type: bigint }
- { name: certificate_id, type: bigint }
- { name: access_list_id, type: bigint }
- { name: ssl_forced, type: boolean }
- { name: caching_enabled, type: boolean }
- { name: block_exploits, type: boolean }
- { name: allow_websocket_upgrade, type: boolean }
- { name: http2_support, type: boolean }
- { name: hsts_enabled, type: boolean }
- { name: hsts_subdomains, type: boolean }
- { name: paths, type: text }
- { name: advanced_config, type: text }
- { name: status, type: text }
- { name: error_message, type: text }
- { name: is_disabled, type: boolean }
- createTable:
name: jwt_keys
columns:
- { name: id, type: bigint, primaryKey: true, autoIncrement: true }
- { name: created_at, type: bigint }
- { name: updated_at, type: bigint }
- { name: is_deleted, type: bigint }
- { name: public_key, type: text }
- { name: private_key, type: text }
- createTable:
name: nginx_template
columns:
- { name: id, type: bigint, primaryKey: true, autoIncrement: true }
- { name: created_at, type: bigint }
- { name: updated_at, type: bigint }
- { name: is_deleted, type: bigint }
- { name: user_id, type: bigint }
- { name: name, type: text }
- { name: type, type: text }
- { name: template, type: text }
- createTable:
name: setting
columns:
- { name: id, type: bigint, primaryKey: true, autoIncrement: true }
- { name: created_at, type: bigint }
- { name: updated_at, type: bigint }
- { name: is_deleted, type: bigint }
- { name: name, type: text }
- { name: description, type: text }
- { name: value, type: json }
- createTable:
name: stream
columns:
- { name: id, type: bigint, primaryKey: true, autoIncrement: true }
- { name: created_at, type: bigint }
- { name: updated_at, type: bigint }
- { name: is_deleted, type: bigint }
- { name: expires_on, type: bigint }
- { name: user_id, type: bigint }
- { name: provider, type: text }
- { name: name, type: text }
- { name: domain_names, type: text }
- { name: meta, type: text }
- createTable:
name: upstream
columns:
- { name: id, type: bigint, primaryKey: true, autoIncrement: true }
- { name: created_at, type: bigint }
- { name: updated_at, type: bigint }
- { name: is_deleted, type: bigint }
- { name: user_id, type: bigint }
- { name: name, type: text }
- { name: nginx_template_id, type: bigint }
- { name: ip_hash, type: boolean }
- { name: ntlm, type: boolean }
- { name: keepalive, type: bigint }
- { name: keepalive_requests, type: bigint }
- { name: keepalive_time, type: text }
- { name: keepalive_timeout, type: text }
- { name: advanced_config, type: text }
- { name: status, type: text }
- { name: error_message, type: text }
- createTable:
name: upstream_server
columns:
- { name: id, type: bigint, primaryKey: true, autoIncrement: true }
- { name: created_at, type: bigint }
- { name: updated_at, type: bigint }
- { name: is_deleted, type: bigint }
- { name: upstream_id, type: bigint }
- { name: server, type: text }
- { name: weight, type: bigint }
- { name: max_conns, type: bigint }
- { name: max_fails, type: bigint }
- { name: fail_timeout, type: bigint }
- { name: is_backup, type: boolean }
- createTable:
name: user
columns:
- { name: id, type: bigint, primaryKey: true, autoIncrement: true }
- { name: created_at, type: bigint }
- { name: updated_at, type: bigint }
- { name: is_deleted, type: bigint }
- { name: name, type: text }
- { name: email, type: text }
- { name: is_disabled, type: boolean }
- { name: is_system, type: boolean }
- createTable:
name: user_has_capability
columns:
- { name: user_id, type: bigint }
- { name: capability_name, type: text }
@@ -0,0 +1,253 @@
# Seed data for a new database.
#
# created_at/updated_at hold unix epoch milliseconds. polyschema always sends
# insert values as bind parameters, so an SQL expression for "now" can't be
# used as a value. The rows are inserted without them, and the sql operations
# at the end fill them in using each engine's own clock.
operations:
# Capabilities
- insert:
table: capability
rows:
- { name: 'full-admin' }
- { name: 'access-lists.view' }
- { name: 'access-lists.manage' }
- { name: 'audit-log.view' }
- { name: 'certificates.view' }
- { name: 'certificates.manage' }
- { name: 'certificate-authorities.view' }
- { name: 'certificate-authorities.manage' }
- { name: 'dns-providers.view' }
- { name: 'dns-providers.manage' }
- { name: 'hosts.view' }
- { name: 'hosts.manage' }
- { name: 'nginx-templates.view' }
- { name: 'nginx-templates.manage' }
- { name: 'settings.manage' }
- { name: 'streams.view' }
- { name: 'streams.manage' }
- { name: 'users.manage' }
# Default settings
- insert:
table: setting
rows:
- { is_deleted: 0, name: 'default-site', description: 'What to show users who hit your Nginx server by default', value: '"welcome"' }
- { is_deleted: 0, name: 'auth-methods', description: 'Which methods are enabled for authentication', value: '["local"]' }
- { is_deleted: 0, name: 'oauth-auth', description: 'Configuration for OAuth authentication', value: '{}' }
- { is_deleted: 0, name: 'ldap-auth', description: 'Configuration for LDAP authentication', value: '{"host": "", "dn": "", "sync_by": "uid"}' }
# Built-in certificate authorities
- insert:
table: certificate_authority
rows:
- { is_deleted: 0, name: 'ZeroSSL', acmesh_server: 'zerossl', max_domains: 10, is_wildcard_supported: true, is_readonly: true }
- { is_deleted: 0, name: 'Let''s Encrypt', acmesh_server: 'https://acme-v02.api.letsencrypt.org/directory', max_domains: 10, is_wildcard_supported: true, is_readonly: true }
- { is_deleted: 0, name: 'Buypass Go SSL', acmesh_server: 'https://api.buypass.com/acme/directory', max_domains: 5, is_wildcard_supported: false, is_readonly: true }
- { is_deleted: 0, name: 'SSL.com', acmesh_server: 'ssl.com', max_domains: 10, is_wildcard_supported: false, is_readonly: true }
- { is_deleted: 0, name: 'Let''s Encrypt (Testing)', acmesh_server: 'https://acme-staging-v02.api.letsencrypt.org/directory', max_domains: 10, is_wildcard_supported: true, is_readonly: true }
- { is_deleted: 0, name: 'Buypass Go SSL (Testing)', acmesh_server: 'https://api.test4.buypass.no/acme/directory', max_domains: 5, is_wildcard_supported: false, is_readonly: true }
# The system user (id 1, owns the built-in nginx templates)
- insert:
table: user
rows:
- { is_deleted: 0, name: 'System', email: 'system@localhost', is_disabled: false, is_system: true }
# Built-in nginx templates
- insert:
table: nginx_template
rows:
- is_deleted: 0
user_id: 1
name: 'Default Proxy Template'
type: 'proxy'
template: |
# ------------------------------------------------------------
{{#each Host.DomainNames}}
# {{this}}
{{/each}}
# ------------------------------------------------------------
server {
{{#if Config.Ipv4}}
listen 80;
{{/if}}
{{#if Config.Ipv6}}
listen [::]:80;
{{/if}}
{{#if Certificate.ID}}
{{#if Config.Ipv4}}
listen 443 ssl {{#if Host.HTTP2Support}}http2{{/if}};
{{/if}}
{{#if Config.Ipv6}}
listen [::]:443 ssl {{#if Host.HTTP2Support}}http2{{/if}};
{{/if}}
{{/if}}
server_name {{#each Host.DomainNames}}{{this}} {{/each}};
{{#if Certificate.ID}}
include conf.d/include/ssl-ciphers.conf;
{{#if Certificate.IsAcme}}
ssl_certificate {{Certificate.Folder}}/fullchain.pem;
ssl_certificate_key {{Certificate.Folder}}/privkey.pem;
{{else}}
# Custom SSL
ssl_certificate /data/custom_ssl/npm-{{Certicicate.ID}}/fullchain.pem;
ssl_certificate_key /data/custom_ssl/npm-{{Certificate.ID}}/privkey.pem;
{{/if}}
{{/if}}
{{#if Host.CachingEnabled}}
include conf.d/include/assets.conf;
{{/if}}
{{#if Host.BlockExploits}}
include conf.d/include/block-exploits.conf;
{{/if}}
{{#if Certificate.ID}}
{{#if Host.SSLForced}}
{{#if Host.HSTSEnabled}}
# HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years)
add_header Strict-Transport-Security "max-age=63072000;{{#if Host.HSTSSubdomains}} includeSubDomains;{{/if}} preload" always;
{{/if}}
# Force SSL
include conf.d/include/force-ssl.conf;
{{/if}}
{{/if}}
{{#if Host.AllowWebsocketUpgrade}}
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_http_version 1.1;
{{/if}}
access_log /data/logs/host-{{Host.ID}}_access.log proxy;
error_log /data/logs/host-{{Host.ID}}_error.log warn;
{{Host.AdvancedConfig}}
# locations ?
# default location:
location / {
{{#if Host.AccessListID}}
# Authorization
auth_basic "Authorization required";
auth_basic_user_file /data/access/{{Host.AccessListID}};
# access_list.passauth ? todo
{{/if}}
# Access Rules ? todo
# Access checks must...? todo
{{#if Certificate.ID}}
{{#if Host.SSLForced}}
{{#if Host.HSTSEnabled}}
# HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years)
add_header Strict-Transport-Security "max-age=63072000;{{#if Host.HSTSSubdomains}} includeSubDomains;{{/if}} preload" always;
{{/if}}
{{/if}}
{{/if}}
{{#if Host.AllowWebsocketUpgrade}}
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
{{/if}}
# Proxy!
add_header X-Served-By $host;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_http_version 1.1;
{{#if Upstream.ID}}
# upstream
proxy_pass {{Host.ProxyScheme}}://npm_upstream_{{Upstream.ID}};
{{else}}
# proxy a single host
proxy_pass {{Host.ProxyScheme}}://{{Host.ProxyHost}}:{{Host.ProxyPort}};
{{/if}}
}
# Legacy Custom Configuration
include /data/nginx/custom/server_proxy[.]conf;
}
- is_deleted: 0
user_id: 1
name: 'Default Redirect Template'
type: 'redirect'
template: '# this is a redirect template'
- is_deleted: 0
user_id: 1
name: 'Default Dead Template'
type: 'dead'
template: '# this is a dead template'
- is_deleted: 0
user_id: 1
name: 'Default Stream Template'
type: 'stream'
template: '# this is a stream template'
- is_deleted: 0
user_id: 1
name: 'Default Upstream Template'
type: 'upstream'
template: |
# ------------------------------------------------------------
# Upstream {{Upstream.ID}}: {{Upstream.Name}}
# ------------------------------------------------------------
upstream npm_upstream_{{Upstream.ID}} {
{{#if Upstream.IPHash~}}
ip_hash;
{{~/if}}
{{#if Upstream.NTLM~}}
ntlm;
{{~/if}}
{{#if Upstream.Keepalive~}}
keepalive {{Upstream.Keepalive}};
{{~/if}}
{{#if Upstream.KeepaliveRequests~}}
keepalive_requests {{Upstream.KeepaliveRequests}};
{{~/if}}
{{#if Upstream.KeepaliveTime~}}
keepalive_time {{Upstream.KeepaliveTime}};
{{~/if}}
{{#if Upstream.KeepaliveTimeout~}}
keepalive_timeout {{Upstream.KeepaliveTimeout}};
{{~/if}}
{{Upstream.AdvancedConfig}}
{{#each Upstream.Servers~}}
{{#unless IsDeleted~}}
server {{Server}} {{#if Weight}}weight={{Weight}} {{/if}}{{#if MaxConns}}max_conns={{MaxConns}} {{/if}}{{#if MaxFails}}max_fails={{MaxFails}} {{/if}}{{#if FailTimeout}}fail_timeout={{FailTimeout}} {{/if}}{{#if Backup}}backup{{/if}};
{{/unless}}
{{/each}}
}
# Timestamps for everything above
- sql:
postgres: 'UPDATE "setting" SET created_at = (extract(epoch from clock_timestamp()) * 1000)::bigint, updated_at = (extract(epoch from clock_timestamp()) * 1000)::bigint'
mysql: 'UPDATE `setting` SET created_at = CAST(UNIX_TIMESTAMP(NOW(3)) * 1000 AS UNSIGNED), updated_at = CAST(UNIX_TIMESTAMP(NOW(3)) * 1000 AS UNSIGNED)'
sqlite: 'UPDATE "setting" SET created_at = CAST((julianday(''now'') - 2440587.5) * 86400000 AS INTEGER), updated_at = CAST((julianday(''now'') - 2440587.5) * 86400000 AS INTEGER)'
- sql:
postgres: 'UPDATE "certificate_authority" SET created_at = (extract(epoch from clock_timestamp()) * 1000)::bigint, updated_at = (extract(epoch from clock_timestamp()) * 1000)::bigint'
mysql: 'UPDATE `certificate_authority` SET created_at = CAST(UNIX_TIMESTAMP(NOW(3)) * 1000 AS UNSIGNED), updated_at = CAST(UNIX_TIMESTAMP(NOW(3)) * 1000 AS UNSIGNED)'
sqlite: 'UPDATE "certificate_authority" SET created_at = CAST((julianday(''now'') - 2440587.5) * 86400000 AS INTEGER), updated_at = CAST((julianday(''now'') - 2440587.5) * 86400000 AS INTEGER)'
- sql:
postgres: 'UPDATE "user" SET created_at = (extract(epoch from clock_timestamp()) * 1000)::bigint, updated_at = (extract(epoch from clock_timestamp()) * 1000)::bigint'
mysql: 'UPDATE `user` SET created_at = CAST(UNIX_TIMESTAMP(NOW(3)) * 1000 AS UNSIGNED), updated_at = CAST(UNIX_TIMESTAMP(NOW(3)) * 1000 AS UNSIGNED)'
sqlite: 'UPDATE "user" SET created_at = CAST((julianday(''now'') - 2440587.5) * 86400000 AS INTEGER), updated_at = CAST((julianday(''now'') - 2440587.5) * 86400000 AS INTEGER)'
- sql:
postgres: 'UPDATE "nginx_template" SET created_at = (extract(epoch from clock_timestamp()) * 1000)::bigint, updated_at = (extract(epoch from clock_timestamp()) * 1000)::bigint'
mysql: 'UPDATE `nginx_template` SET created_at = CAST(UNIX_TIMESTAMP(NOW(3)) * 1000 AS UNSIGNED), updated_at = CAST(UNIX_TIMESTAMP(NOW(3)) * 1000 AS UNSIGNED)'
sqlite: 'UPDATE "nginx_template" SET created_at = CAST((julianday(''now'') - 2440587.5) * 86400000 AS INTEGER), updated_at = CAST((julianday(''now'') - 2440587.5) * 86400000 AS INTEGER)'
+13 -12
View File
@@ -1,6 +1,6 @@
module npm
go 1.26.0
go 1.27.1
require (
github.com/DATA-DOG/go-sqlmock v1.5.2
@@ -12,11 +12,11 @@ require (
github.com/go-chi/chi/v5 v5.3.2
github.com/go-chi/cors v1.2.2
github.com/go-chi/jwtauth/v5 v5.4.0
github.com/go-gormigrate/gormigrate/v2 v2.1.7
github.com/go-ldap/ldap/v3 v3.4.14
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/jc21/go-sse v1.7.0
github.com/jc21/jsref v0.0.0-20250501111625-0ce4620b7d96
github.com/jc21/polyschema v0.0.0-20260925050705-5d30b3cbbc99
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/qri-io/jsonschema v0.2.1
github.com/rotisserie/eris v0.5.4
@@ -34,19 +34,19 @@ require (
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
filippo.io/edwards25519 v1.2.0 // indirect
github.com/Azure/go-ntlmssp v0.1.1 // indirect
github.com/alexflint/go-scalar v1.2.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/glebarez/go-sqlite v1.23.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/go-sql-driver/mysql v1.10.1 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.10.0 // indirect
github.com/jackc/pgx/v5 v5.11.0 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
@@ -60,21 +60,22 @@ require (
github.com/lestrrat-go/structinfo v0.0.0-20210312050401-7f8bd69d6acb // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/mattn/go-sqlite3 v1.14.28 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/qri-io/jsonpointer v0.1.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/stretchr/objx v0.5.3 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
golang.org/x/sync v0.23.0 // indirect
golang.org/x/sys v0.48.0 // indirect
golang.org/x/text v0.42.0 // indirect
modernc.org/libc v1.61.0 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/sqlite v1.28.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.74.1 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.55.0 // indirect
)
replace modernc.org/sqlite => gitlab.com/jc21com/sqlite v1.22.2-0.20230527022643-b56cedb3bc85
+50 -38
View File
@@ -1,5 +1,5 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
github.com/Azure/go-ntlmssp v0.1.1 h1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw=
github.com/Azure/go-ntlmssp v0.1.1/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
@@ -23,8 +23,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/go-sqlite v1.23.0 h1:FyhIq4jqmgphQAUlY79zPldYGwISEZikaDfhiGWkkaI=
github.com/glebarez/go-sqlite v1.23.0/go.mod h1:IIYrOH3L0rHY3jb4IXOHoWdklNajSGUN2eJcvK8WrnI=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-asn1-ber/asn1-ber v1.5.8 h1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ=
@@ -35,12 +35,10 @@ github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/jwtauth/v5 v5.4.0 h1:Ieh0xMJsFvqylqJ02/mQHKzbbKO9DYNBh4DPKCwTwYI=
github.com/go-chi/jwtauth/v5 v5.4.0/go.mod h1:w6yjqUUXz1b8+oiJel64Sz1KJwduQM6qUA5QNzO5+bQ=
github.com/go-gormigrate/gormigrate/v2 v2.1.7 h1:PdT4jVPbRb4R+0Ey2R0yJOdctVf4Whiq1Qi4necaZdg=
github.com/go-gormigrate/gormigrate/v2 v2.1.7/go.mod h1:3ouXglTuPrKF5+7cQyVGfvAXTU4vLMaYh9+EPl03uog=
github.com/go-ldap/ldap/v3 v3.4.14 h1:D6PYdEgsaVzsXyr6w/yDC06Ria4uUhWm+Rb+er8lfAs=
github.com/go-ldap/ldap/v3 v3.4.14/go.mod h1:S4eJUMUNjDkE0ZJtIZdybwyb03sGGLW6gxXT1Hs8VKA=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME=
github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
@@ -49,24 +47,28 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/pgx/v5 v5.11.0 h1:IzBBtyK9AHqf98cctWFifYSci2hgQR/cd56wB4p+ogg=
github.com/jackc/pgx/v5 v5.11.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jc21/go-sse v1.7.0 h1:Lavb7FGssS2UdJMK1P3r4rQJgg2JZx7BINi8pd/QzKg=
github.com/jc21/go-sse v1.7.0/go.mod h1:dbA0LtDgvSEhlAXz+meN1KAcdvcCLiF0aVdhjhZjbGI=
github.com/jc21/jsref v0.0.0-20250501111625-0ce4620b7d96 h1:LzBXyNdALQHC7DUy8PE7IFTihmnSRop4Ps2cBIr/WP8=
github.com/jc21/jsref v0.0.0-20250501111625-0ce4620b7d96/go.mod h1:Vno1sw0yqqImXnYy3q6ueIG+h+Vwwfbkfw9DTHCrHfY=
github.com/jc21/polyschema v0.0.0-20260925050705-5d30b3cbbc99 h1:HOjJaUJjZehxSMoLxhg8sMnTkk/5ncFy+5gYnijZg60=
github.com/jc21/polyschema v0.0.0-20260925050705-5d30b3cbbc99/go.mod h1:H8UH9o8FM1uvKiA8kd9daeeKLC5x5xxqiPtyIYoxFns=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
@@ -86,8 +88,11 @@ github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lestrrat-go/blackmagic v1.0.3 h1:94HXkVLxkZO9vJI/w2u1T0DAoprShFd13xtnSINtDWs=
github.com/lestrrat-go/blackmagic v1.0.3/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
@@ -111,12 +116,12 @@ github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stg
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA=
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
@@ -131,6 +136,8 @@ github.com/qri-io/jsonschema v0.2.1 h1:NNFoKms+kut6ABPf6xiKNM5214jzxAhDBrPHCJ97W
github.com/qri-io/jsonschema v0.2.1/go.mod h1:g7DPkiOsK1xv6T/Ao5scXRkd+yTFygcANPBaaqW+VrI=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
github.com/rotisserie/eris v0.5.4 h1:Il6IvLdAapsMhvuOahHWiBnl1G++Q0/L5UIkI5mARSk=
github.com/rotisserie/eris v0.5.4/go.mod h1:Z/kgYTJiJtocxCbFfvRmO+QejApzG6zpyky9G1A4g9s=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
@@ -165,8 +172,6 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
@@ -184,9 +189,12 @@ golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/datatypes v1.2.7 h1:ww9GAhF1aGXZY3EB3cJPJ7//JiuQo7DlQA7NNlVaTdk=
gorm.io/datatypes v1.2.7/go.mod h1:M2iO+6S3hhi4nAyYe444Pcb0dcIiOMJ7QHaUXxyiNZY=
@@ -205,25 +213,29 @@ gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo=
gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
gorm.io/plugin/soft_delete v1.2.1 h1:qx9D/c4Xu6w5KT8LviX8DgLcB9hkKl6JC9f44Tj7cGU=
gorm.io/plugin/soft_delete v1.2.1/go.mod h1:Zv7vQctOJTGOsJ/bWgrN1n3od0GBAZgnLjEx+cApLGk=
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4=
modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/libc v1.61.0 h1:eGFcvWpqlnoGwzZeZe3PWJkkKbM/3SUGyk1DVZQ0TpE=
modernc.org/libc v1.61.0/go.mod h1:DvxVX89wtGTu+r72MLGhygpfi3aUGgZRdAYGCAVVud0=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/cc/v4 v4.29.0 h1:CXgwL8cvxmyzBQZzbSl/6xFtMCryb6u8IOqDci39cgc=
modernc.org/cc/v4 v4.29.0/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI=
modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.74.1 h1:bdR4VTKFMC4966QSNZ05XLGI/VwzVa2kTUX51Dm0riQ=
modernc.org/libc v1.74.1/go.mod h1:uH4t5bOx3G3g9Xcmj10YKlTcVISlRDwv8VoQJG9n8Os=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+2 -35
View File
@@ -2,32 +2,17 @@ package handler
import (
"encoding/json"
"fmt"
"net/http"
c "npm/internal/api/context"
h "npm/internal/api/http"
"npm/internal/api/middleware"
"npm/internal/entity/accesslist"
)
// GetAccessLists will return a list of Access Lists
// Route: GET /access-lists
func GetAccessLists() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
pageInfo, err := getPageInfoFromRequest(r)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
items, err := accesslist.List(pageInfo, middleware.GetFiltersFromContext(r))
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, items)
}
}
return listHandler(accesslist.List)
}
// GetAccessList will return a single access list
@@ -54,25 +39,7 @@ func GetAccessList() func(http.ResponseWriter, *http.Request) {
// Route: POST /access-lists
func CreateAccessList() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
var newItem accesslist.Model
err := json.Unmarshal(bodyBytes, &newItem)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
// Get userID from token
userID, _ := r.Context().Value(c.UserIDCtxKey).(uint)
newItem.UserID = userID
if err = newItem.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, fmt.Sprintf("Unable to save Access List: %s", err.Error()), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, newItem)
createUserOwned(w, r, &accesslist.Model{}, "Access List")
}
}
+1 -1
View File
@@ -231,7 +231,7 @@ func RefreshToken() func(http.ResponseWriter, *http.Request) {
// Route: POST /auth/sse
func NewSSEToken() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
userID := r.Context().Value(c.UserIDCtxKey).(uint)
userID, _ := r.Context().Value(c.UserIDCtxKey).(uint)
// Find user
userObj, userErr := user.GetByID(userID)
+169
View File
@@ -0,0 +1,169 @@
package handler
import (
"encoding/json"
"fmt"
"net/http"
c "npm/internal/api/context"
h "npm/internal/api/http"
"npm/internal/api/middleware"
"npm/internal/entity"
"npm/internal/model"
"gorm.io/gorm"
)
// userOwned is an entity that belongs to the user that created it
type userOwned interface {
SetUserID(userID uint)
Save() error
}
// createUserOwned decodes the request body into item, assigns it to the
// requesting user and saves it. label is used in the error message.
func createUserOwned(w http.ResponseWriter, r *http.Request, item userOwned, label string) {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
if err := json.Unmarshal(bodyBytes, item); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
// Get userID from token
userID, _ := r.Context().Value(c.UserIDCtxKey).(uint)
item.SetUserID(userID)
if err := item.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, fmt.Sprintf("Unable to save %s: %s", label, err.Error()), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, item)
}
// getNginxConfig returns a handler that loads an entity by the ID in the
// paramName url param and responds with its nginx config from disk, as
// plain text when format is "text" or JSON otherwise
func getNginxConfig[T any](
format string,
paramName string,
getByID func(uint) (T, error),
getContent func(T) (string, error),
) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
id, err := getURLParamInt(r, paramName)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
item, err := getByID(id)
switch err {
case gorm.ErrRecordNotFound:
h.NotFound(w, r)
case nil:
// Get the config from disk
content, nErr := getContent(item)
if nErr != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, nErr.Error(), nil)
return
}
if format == "text" {
h.ResultResponseText(w, http.StatusOK, content)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, content)
default:
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
}
}
}
// getByIDHandler returns a handler that responds with the entity loaded by
// the ID in the paramName url param
func getByIDHandler[T any](paramName string, getByID func(uint) (T, error)) func(http.ResponseWriter, *http.Request) {
return withEntity(paramName, getByID, func(w http.ResponseWriter, r *http.Request, item *T) {
h.ResultResponseJSON(w, r, http.StatusOK, item)
})
}
// updateByIDHandler returns a handler that loads the entity by the ID in the
// paramName url param, decodes the request body over it and saves it
func updateByIDHandler[T any](
paramName string,
getByID func(uint) (T, error),
save func(*T) error,
) func(http.ResponseWriter, *http.Request) {
return withEntity(paramName, getByID, func(w http.ResponseWriter, r *http.Request, item *T) {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
if err := json.Unmarshal(bodyBytes, item); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
if err := save(item); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, item)
})
}
// deleteByIDHandler returns a handler that loads the entity by the ID in the
// paramName url param, deletes it and responds with the result of del
func deleteByIDHandler[T, R any](
paramName string,
getByID func(uint) (T, error),
del func(*T) R,
) func(http.ResponseWriter, *http.Request) {
return withEntity(paramName, getByID, func(w http.ResponseWriter, r *http.Request, item *T) {
h.ResultResponseJSON(w, r, http.StatusOK, del(item))
})
}
// withEntity returns a handler that loads the entity by the ID in the
// paramName url param and passes it to fn, responding with not found or
// bad request when it can't be loaded
func withEntity[T any](
paramName string,
getByID func(uint) (T, error),
fn func(http.ResponseWriter, *http.Request, *T),
) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
id, err := getURLParamInt(r, paramName)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
item, err := getByID(id)
switch err {
case gorm.ErrRecordNotFound:
h.NotFound(w, r)
case nil:
fn(w, r, &item)
default:
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
}
}
}
// listHandler returns a handler that responds with a page of entities
// matching the request's pagination and filters
func listHandler(list func(model.PageInfo, []model.Filter) (entity.ListResponse, error)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
pageInfo, err := getPageInfoFromRequest(r)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
items, err := list(pageInfo, middleware.GetFiltersFromContext(r))
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, items)
}
}
}
+5 -105
View File
@@ -1,144 +1,44 @@
package handler
import (
"encoding/json"
"fmt"
"net/http"
c "npm/internal/api/context"
h "npm/internal/api/http"
"npm/internal/api/middleware"
"npm/internal/dnsproviders"
"npm/internal/entity/dnsprovider"
"npm/internal/errors"
"gorm.io/gorm"
)
// GetDNSProviders will return a list of DNS Providers
// Route: GET /dns-providers
func GetDNSProviders() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
pageInfo, err := getPageInfoFromRequest(r)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
items, err := dnsprovider.List(pageInfo, middleware.GetFiltersFromContext(r))
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, items)
}
}
return listHandler(dnsprovider.List)
}
// GetDNSProvider will return a single DNS Provider
// Route: GET /dns-providers/{providerID}
func GetDNSProvider() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var providerID uint
if providerID, err = getURLParamInt(r, "providerID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
item, err := dnsprovider.GetByID(providerID)
switch err {
case gorm.ErrRecordNotFound:
h.NotFound(w, r)
case nil:
h.ResultResponseJSON(w, r, http.StatusOK, item)
default:
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
}
}
return getByIDHandler("providerID", dnsprovider.GetByID)
}
// CreateDNSProvider will create a DNS Provider
// Route: POST /dns-providers
func CreateDNSProvider() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
var newItem dnsprovider.Model
err := json.Unmarshal(bodyBytes, &newItem)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
// Get userID from token
userID, _ := r.Context().Value(c.UserIDCtxKey).(uint)
newItem.UserID = userID
if err = newItem.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, fmt.Sprintf("Unable to save DNS Provider: %s", err.Error()), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, newItem)
createUserOwned(w, r, &dnsprovider.Model{}, "DNS Provider")
}
}
// UpdateDNSProvider updates a provider
// Route: PUT /dns-providers/{providerID}
func UpdateDNSProvider() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var providerID uint
if providerID, err = getURLParamInt(r, "providerID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
item, err := dnsprovider.GetByID(providerID)
switch err {
case gorm.ErrRecordNotFound:
h.NotFound(w, r)
case nil:
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
err := json.Unmarshal(bodyBytes, &item)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
if err = item.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, item)
default:
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
}
}
return updateByIDHandler("providerID", dnsprovider.GetByID, (*dnsprovider.Model).Save)
}
// DeleteDNSProvider removes a provider
// Route: DELETE /dns-providers/{providerID}
func DeleteDNSProvider() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var providerID uint
if providerID, err = getURLParamInt(r, "providerID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
item, err := dnsprovider.GetByID(providerID)
switch err {
case gorm.ErrRecordNotFound:
h.NotFound(w, r)
case nil:
h.ResultResponseJSON(w, r, http.StatusOK, item.Delete())
default:
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
}
}
return deleteByIDHandler("providerID", dnsprovider.GetByID, (*dnsprovider.Model).Delete)
}
// GetAcmeshProviders will return a list of acme.sh providers
+1 -28
View File
@@ -172,34 +172,7 @@ func DeleteHost() func(http.ResponseWriter, *http.Request) {
// Route: GET /hosts/{hostID}/nginx-config
// Route: GET /hosts/{hostID}/nginx-config.txt
func GetHostNginxConfig(format string) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var hostID uint
if hostID, err = getURLParamInt(r, "hostID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
item, err := host.GetByID(hostID)
switch err {
case gorm.ErrRecordNotFound:
h.NotFound(w, r)
case nil:
// Get the config from disk
content, nErr := nginx.GetHostConfigContent(item)
if nErr != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, nErr.Error(), nil)
return
}
if format == "text" {
h.ResultResponseText(w, http.StatusOK, content)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, content)
default:
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
}
}
return getNginxConfig(format, "hostID", host.GetByID, nginx.GetHostConfigContent)
}
func configureHost(hst host.Model) {
+6 -108
View File
@@ -1,142 +1,40 @@
//nolint:dupl // thin wiring of the shared CRUD helpers in crud.go
package handler
import (
"encoding/json"
"fmt"
"net/http"
c "npm/internal/api/context"
h "npm/internal/api/http"
"npm/internal/api/middleware"
"npm/internal/entity/nginxtemplate"
"gorm.io/gorm"
)
// GetNginxTemplates will return a list of Nginx Templates
// Route: GET /nginx-templates
func GetNginxTemplates() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
pageInfo, err := getPageInfoFromRequest(r)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
items, err := nginxtemplate.List(pageInfo, middleware.GetFiltersFromContext(r))
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, items)
}
}
return listHandler(nginxtemplate.List)
}
// GetNginxTemplate will return a single Nginx Template
// Route: GET /nginx-templates/{templateID}
func GetNginxTemplate() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var templateID uint
if templateID, err = getURLParamInt(r, "templateID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
item, err := nginxtemplate.GetByID(templateID)
switch err {
case gorm.ErrRecordNotFound:
h.NotFound(w, r)
case nil:
h.ResultResponseJSON(w, r, http.StatusOK, item)
default:
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
}
}
return getByIDHandler("templateID", nginxtemplate.GetByID)
}
// CreateNginxTemplate will create a Nginx Template
// Route: POST /nginx-templates
func CreateNginxTemplate() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
var newNginxTemplate nginxtemplate.Model
err := json.Unmarshal(bodyBytes, &newNginxTemplate)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
// Get userID from token
userID, _ := r.Context().Value(c.UserIDCtxKey).(uint)
newNginxTemplate.UserID = userID
if err = newNginxTemplate.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, fmt.Sprintf("Unable to save Nginx Template: %s", err.Error()), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, newNginxTemplate)
createUserOwned(w, r, &nginxtemplate.Model{}, "Nginx Template")
}
}
// UpdateNginxTemplate updates a nginx template
// Route: PUT /nginx-templates/{templateID}
func UpdateNginxTemplate() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var templateID uint
if templateID, err = getURLParamInt(r, "templateID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
// reconfigure, _ := getQueryVarBool(r, "reconfigure", false, false)
nginxTemplate, err := nginxtemplate.GetByID(templateID)
switch err {
case gorm.ErrRecordNotFound:
h.NotFound(w, r)
case nil:
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
err := json.Unmarshal(bodyBytes, &nginxTemplate)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
if err = nginxTemplate.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, nginxTemplate)
default:
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
}
}
return updateByIDHandler("templateID", nginxtemplate.GetByID, (*nginxtemplate.Model).Save)
}
// DeleteNginxTemplate removes a nginx template
// Route: DELETE /nginx-templates/{templateID}
func DeleteNginxTemplate() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var templateID uint
if templateID, err = getURLParamInt(r, "templateID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
item, err := nginxtemplate.GetByID(templateID)
switch err {
case gorm.ErrRecordNotFound:
h.NotFound(w, r)
case nil:
h.ResultResponseJSON(w, r, http.StatusOK, item.Delete())
default:
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
}
}
return deleteByIDHandler("templateID", nginxtemplate.GetByID, (*nginxtemplate.Model).Delete)
}
+3 -2
View File
@@ -35,12 +35,13 @@ func NotFound() func(http.ResponseWriter, *http.Request) {
}
err := tryRead(assetsSub, path, w)
if err == errIsDir {
switch err {
case errIsDir:
err = tryRead(assetsSub, defaultFile, w)
if err != nil {
h.NotFound(w, r)
}
} else if err == nil {
case nil:
return
}
+5 -8
View File
@@ -5,7 +5,6 @@ import (
"fmt"
"net/http"
"net/url"
"strings"
h "npm/internal/api/http"
"npm/internal/entity/auth"
@@ -15,17 +14,15 @@ import (
njwt "npm/internal/jwt"
"npm/internal/logger"
chiMiddleware "github.com/go-chi/chi/v5/middleware"
"gorm.io/gorm"
)
// getRequestIPAddress will use X-FORWARDED-FOR header if it exists
// otherwise it will use RemoteAddr
// getRequestIPAddress will use the client IP set by the router's client IP
// middleware if it exists, otherwise it will use RemoteAddr
func getRequestIPAddress(r *http.Request) string {
// this Get is case insensitive
xff := r.Header.Get("X-FORWARDED-FOR")
if xff != "" {
ip, _, _ := strings.Cut(xff, ",")
return strings.TrimSpace(ip)
if ip := chiMiddleware.GetClientIP(r.Context()); ip != "" {
return ip
}
return r.RemoteAddr
}
+6 -106
View File
@@ -1,140 +1,40 @@
//nolint:dupl // thin wiring of the shared CRUD helpers in crud.go
package handler
import (
"encoding/json"
"fmt"
"net/http"
c "npm/internal/api/context"
h "npm/internal/api/http"
"npm/internal/api/middleware"
"npm/internal/entity/stream"
"gorm.io/gorm"
)
// GetStreams will return a list of Streams
// Route: GET /hosts/streams
func GetStreams() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
pageInfo, err := getPageInfoFromRequest(r)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
hosts, err := stream.List(pageInfo, middleware.GetFiltersFromContext(r))
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, hosts)
}
}
return listHandler(stream.List)
}
// GetStream will return a single Streams
// Route: GET /hosts/streams/{hostID}
func GetStream() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var hostID uint
if hostID, err = getURLParamInt(r, "hostID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
item, err := stream.GetByID(hostID)
switch err {
case gorm.ErrRecordNotFound:
h.NotFound(w, r)
case nil:
h.ResultResponseJSON(w, r, http.StatusOK, item)
default:
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
}
}
return getByIDHandler("hostID", stream.GetByID)
}
// CreateStream will create a Stream
// Route: POST /hosts/steams
func CreateStream() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
var newHost stream.Model
err := json.Unmarshal(bodyBytes, &newHost)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
// Get userID from token
userID, _ := r.Context().Value(c.UserIDCtxKey).(uint)
newHost.UserID = userID
if err = newHost.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, fmt.Sprintf("Unable to save Stream: %s", err.Error()), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, newHost)
createUserOwned(w, r, &stream.Model{}, "Stream")
}
}
// UpdateStream updates a stream
// Route: PUT /hosts/streams/{hostID}
func UpdateStream() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var hostID uint
if hostID, err = getURLParamInt(r, "hostID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
host, err := stream.GetByID(hostID)
switch err {
case gorm.ErrRecordNotFound:
h.NotFound(w, r)
case nil:
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
err := json.Unmarshal(bodyBytes, &host)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
if err = host.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, host)
default:
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
}
}
return updateByIDHandler("hostID", stream.GetByID, (*stream.Model).Save)
}
// DeleteStream removes a stream
// Route: DELETE /hosts/streams/{hostID}
func DeleteStream() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var hostID uint
if hostID, err = getURLParamInt(r, "hostID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
item, err := stream.GetByID(hostID)
switch err {
case gorm.ErrRecordNotFound:
h.NotFound(w, r)
case nil:
h.ResultResponseJSON(w, r, http.StatusOK, item.Delete())
default:
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
}
}
return deleteByIDHandler("hostID", stream.GetByID, (*stream.Model).Delete)
}
+1 -28
View File
@@ -171,34 +171,7 @@ func DeleteUpstream() func(http.ResponseWriter, *http.Request) {
// Route: GET /upstreams/{upstreamID}/nginx-config
// Route: GET /upstreams/{upstreamID}/nginx-config.txt
func GetUpstreamNginxConfig(format string) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var upstreamID uint
if upstreamID, err = getURLParamInt(r, "upstreamID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
item, err := upstream.GetByID(upstreamID)
switch err {
case gorm.ErrRecordNotFound:
h.NotFound(w, r)
case nil:
// Get the config from disk
content, nErr := nginx.GetUpstreamConfigContent(item)
if nErr != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, nErr.Error(), nil)
return
}
if format == "text" {
h.ResultResponseText(w, http.StatusOK, content)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, content)
default:
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
}
}
return getNginxConfig(format, "upstreamID", upstream.GetByID, nginx.GetUpstreamConfigContent)
}
func configureUpstream(u upstream.Model) {
+1 -1
View File
@@ -70,7 +70,7 @@ func Enforce(permissions ...string) func(http.Handler) http.Handler {
var userCapabilities []string
if found {
userCapabilities = cacheItem.([]string)
userCapabilities, _ = cacheItem.([]string)
} else {
// Get from db and store it
userCapabilities, err = user.GetCapabilities(userID)
@@ -27,7 +27,7 @@ func TestBodyContext(t *testing.T) {
// Create a test handler that checks the context for the body data
handler := http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
bodyData := r.Context().Value(c.BodyCtxKey).([]byte)
bodyData, _ := r.Context().Value(c.BodyCtxKey).([]byte)
assert.Equal(t, body, bodyData)
})
@@ -58,16 +58,13 @@ func listQuerySort(
return ctx, 0, ""
}
// Split sort fields up in to slice
sorts := strings.Split(sortString, ",")
for _, sortItem := range sorts {
// Split sort fields up
for sortItem := range strings.SplitSeq(sortString, ",") {
if strings.Contains(sortItem, ".") {
theseItems := strings.Split(sortItem, ".")
switch strings.ToLower(theseItems[1]) {
case "desc":
fallthrough
case "descending":
case "desc", "descending":
theseItems[1] = "DESC"
default:
theseItems[1] = "ASC"
+2 -2
View File
@@ -38,8 +38,8 @@ func SSEAuth(next http.Handler) http.Handler {
return
}
iss, _ := token.Get("iss")
if iss != "sse" {
iss, ok := token.Issuer()
if !ok || iss != "sse" {
h.ResultErrorJSON(w, r, http.StatusUnauthorized, "Unauthorised", nil)
return
}
+63
View File
@@ -0,0 +1,63 @@
package api
import (
"net/http"
"npm/internal/api/middleware"
"github.com/go-chi/chi/v5"
)
// resourceRoutes describes the standard list/create/get/update/delete
// endpoints for an entity
type resourceRoutes struct {
// idParam is the url param name for a specific item, eg "hostID"
idParam string
// model is the entity used to validate list query sorting and filtering
model any
// viewCapability is required to list and get items
viewCapability string
// manageCapability is required to create, update and delete items
manageCapability string
createSchema string
// updateSchema is optional, the request body isn't validated when empty
updateSchema string
list http.HandlerFunc
create http.HandlerFunc
get http.HandlerFunc
update http.HandlerFunc
del http.HandlerFunc
// manageItem optionally registers extra routes on a specific item that
// require manageCapability
manageItem func(chi.Router)
}
// mount registers the resource's routes on r
func (rr resourceRoutes) mount(r chi.Router) {
// List
r.With(
middleware.Enforce(rr.viewCapability),
middleware.ListQuery(rr.model),
).Get("/", rr.list)
// Create
r.With(middleware.Enforce(rr.manageCapability), middleware.EnforceRequestSchema(rr.createSchema)).
Post("/", rr.create)
// Specific Item
r.Route("/{"+rr.idParam+":[0-9]+}", func(r chi.Router) {
r.With(middleware.Enforce(rr.viewCapability)).
Get("/", rr.get)
r.With(middleware.Enforce(rr.manageCapability)).Route("/", func(r chi.Router) {
r.Delete("/", rr.del)
if rr.updateSchema != "" {
r.With(middleware.EnforceRequestSchema(rr.updateSchema)).Put("/", rr.update)
} else {
r.Put("/", rr.update)
}
if rr.manageItem != nil {
rr.manageItem(r)
}
})
})
}
+119 -188
View File
@@ -53,7 +53,9 @@ func NewRouter() http.Handler {
corss.Handler,
middleware.Cors(r),
middleware.Options(r),
chiMiddleware.RealIP,
// nginx in front of the api overwrites X-Forwarded-For with the
// connecting address, so the last value is the only trusted one
chiMiddleware.ClientIPFromHeader("X-Forwarded-For"),
chiMiddleware.Recoverer,
chiMiddleware.Throttle(5),
middleware.PrettyPrint,
@@ -170,51 +172,35 @@ func applyRoutes(r chi.Router) chi.Router {
})
// Access Lists
r.With(middleware.EnforceSetup()).Route("/access-lists", func(r chi.Router) {
// List
r.With(
middleware.Enforce(user.CapabilityAccessListsView),
middleware.ListQuery(accesslist.Model{}),
).Get("/", handler.GetAccessLists())
// Create
r.With(middleware.Enforce(user.CapabilityAccessListsManage), middleware.EnforceRequestSchema(schema.CreateAccessList())).
Post("/", handler.CreateAccessList())
// Specific Item
r.Route("/{accessListID:[0-9]+}", func(r chi.Router) {
r.With(middleware.Enforce(user.CapabilityAccessListsView)).
Get("/", handler.GetAccessList())
r.With(middleware.Enforce(user.CapabilityAccessListsManage)).Route("/", func(r chi.Router) {
r.Delete("/{accessListID:[0-9]+}", handler.DeleteAccessList())
r.With(middleware.EnforceRequestSchema(schema.UpdateAccessList())).
Put("/{accessListID:[0-9]+}", handler.UpdateAccessList())
})
})
})
r.With(middleware.EnforceSetup()).Route("/access-lists", resourceRoutes{
idParam: "accessListID",
model: accesslist.Model{},
viewCapability: user.CapabilityAccessListsView,
manageCapability: user.CapabilityAccessListsManage,
createSchema: schema.CreateAccessList(),
updateSchema: schema.UpdateAccessList(),
list: handler.GetAccessLists(),
create: handler.CreateAccessList(),
get: handler.GetAccessList(),
update: handler.UpdateAccessList(),
del: handler.DeleteAccessList(),
}.mount)
// DNS Providers
r.With(middleware.EnforceSetup()).Route("/dns-providers", func(r chi.Router) {
// List
r.With(
middleware.Enforce(user.CapabilityDNSProvidersView),
middleware.ListQuery(dnsprovider.Model{}),
).Get("/", handler.GetDNSProviders())
// Create
r.With(middleware.Enforce(user.CapabilityDNSProvidersManage), middleware.EnforceRequestSchema(schema.CreateDNSProvider())).
Post("/", handler.CreateDNSProvider())
// Specific Item
r.Route("/{providerID:[0-9]+}", func(r chi.Router) {
r.With(middleware.Enforce(user.CapabilityDNSProvidersView)).
Get("/{providerID:[0-9]+}", handler.GetDNSProvider())
r.With(middleware.Enforce(user.CapabilityDNSProvidersManage)).Route("/", func(r chi.Router) {
r.Delete("/", handler.DeleteDNSProvider())
r.With(middleware.EnforceRequestSchema(schema.UpdateDNSProvider())).
Put("/{providerID:[0-9]+}", handler.UpdateDNSProvider())
})
})
resourceRoutes{
idParam: "providerID",
model: dnsprovider.Model{},
viewCapability: user.CapabilityDNSProvidersView,
manageCapability: user.CapabilityDNSProvidersManage,
createSchema: schema.CreateDNSProvider(),
updateSchema: schema.UpdateDNSProvider(),
list: handler.GetDNSProviders(),
create: handler.CreateDNSProvider(),
get: handler.GetDNSProvider(),
update: handler.UpdateDNSProvider(),
del: handler.DeleteDNSProvider(),
}.mount(r)
// List Acme DNS Providers
r.With(middleware.Enforce(user.CapabilityDNSProvidersView)).Route("/acmesh", func(r chi.Router) {
@@ -224,161 +210,106 @@ func applyRoutes(r chi.Router) chi.Router {
})
// Certificate Authorities
r.With(middleware.EnforceSetup()).Route("/certificate-authorities", func(r chi.Router) {
// List
r.With(
middleware.Enforce(user.CapabilityCertificateAuthoritiesView),
middleware.ListQuery(certificateauthority.Model{}),
).Get("/", handler.GetCertificateAuthorities())
// Create
r.With(middleware.Enforce(user.CapabilityCertificateAuthoritiesManage), middleware.EnforceRequestSchema(schema.CreateCertificateAuthority())).
Post("/", handler.CreateCertificateAuthority())
// Specific Item
r.Route("/{caID:[0-9]+}", func(r chi.Router) {
r.With(middleware.Enforce(user.CapabilityCertificateAuthoritiesView)).
Get("/", handler.GetCertificateAuthority())
r.With(middleware.EnforceRequestSchema(schema.UpdateCertificateAuthority())).
Put("/", handler.UpdateCertificateAuthority())
r.With(middleware.Enforce(user.CapabilityCertificateAuthoritiesManage)).
Delete("/", handler.DeleteCertificateAuthority())
r.With(middleware.Enforce(user.CapabilityCertificateAuthoritiesManage)).Route("/", func(r chi.Router) {
r.Delete("/{caID:[0-9]+}", handler.DeleteCertificateAuthority())
r.With(middleware.EnforceRequestSchema(schema.UpdateCertificateAuthority())).
Put("/{caID:[0-9]+}", handler.UpdateCertificateAuthority())
})
})
})
r.With(middleware.EnforceSetup()).Route("/certificate-authorities", resourceRoutes{
idParam: "caID",
model: certificateauthority.Model{},
viewCapability: user.CapabilityCertificateAuthoritiesView,
manageCapability: user.CapabilityCertificateAuthoritiesManage,
createSchema: schema.CreateCertificateAuthority(),
updateSchema: schema.UpdateCertificateAuthority(),
list: handler.GetCertificateAuthorities(),
create: handler.CreateCertificateAuthority(),
get: handler.GetCertificateAuthority(),
update: handler.UpdateCertificateAuthority(),
del: handler.DeleteCertificateAuthority(),
}.mount)
// Certificates
r.With(middleware.EnforceSetup()).Route("/certificates", func(r chi.Router) {
// List
r.With(
middleware.Enforce(user.CapabilityCertificatesView),
middleware.ListQuery(certificate.Model{}),
).Get("/", handler.GetCertificates())
// Create
r.With(middleware.Enforce(user.CapabilityCertificatesManage), middleware.EnforceRequestSchema(schema.CreateCertificate())).
Post("/", handler.CreateCertificate())
// Specific Item
r.Route("/{certificateID:[0-9]+}", func(r chi.Router) {
r.With(middleware.Enforce(user.CapabilityCertificatesView)).
Get("/", handler.GetCertificate())
r.With(middleware.Enforce(user.CapabilityCertificatesManage)).Route("/", func(r chi.Router) {
r.Delete("/", handler.DeleteCertificate())
r.Put("/", handler.UpdateCertificate())
// r.With(middleware.EnforceRequestSchema(schema.UpdateCertificate())).
// Put("/", handler.UpdateCertificate())
r.Post("/renew", handler.RenewCertificate())
r.Get("/download", handler.DownloadCertificate())
})
})
})
r.With(middleware.EnforceSetup()).Route("/certificates", resourceRoutes{
idParam: "certificateID",
model: certificate.Model{},
viewCapability: user.CapabilityCertificatesView,
manageCapability: user.CapabilityCertificatesManage,
createSchema: schema.CreateCertificate(),
// updateSchema: schema.UpdateCertificate(),
list: handler.GetCertificates(),
create: handler.CreateCertificate(),
get: handler.GetCertificate(),
update: handler.UpdateCertificate(),
del: handler.DeleteCertificate(),
manageItem: func(r chi.Router) {
r.Post("/renew", handler.RenewCertificate())
r.Get("/download", handler.DownloadCertificate())
},
}.mount)
// Hosts
r.With(middleware.EnforceSetup()).Route("/hosts", func(r chi.Router) {
// List
r.With(
middleware.Enforce(user.CapabilityHostsView),
middleware.ListQuery(host.Model{}),
).Get("/", handler.GetHosts())
// Create
r.With(middleware.Enforce(user.CapabilityHostsManage), middleware.EnforceRequestSchema(schema.CreateHost())).
Post("/", handler.CreateHost())
// Specific Item
r.Route("/{hostID:[0-9]+}", func(r chi.Router) {
r.With(middleware.Enforce(user.CapabilityHostsView)).
Get("/", handler.GetHost())
r.With(middleware.Enforce(user.CapabilityHostsManage)).Route("/", func(r chi.Router) {
r.Delete("/", handler.DeleteHost())
r.With(middleware.EnforceRequestSchema(schema.UpdateHost())).
Put("/", handler.UpdateHost())
r.Get("/nginx-config", handler.GetHostNginxConfig("json"))
r.Get("/nginx-config.txt", handler.GetHostNginxConfig("text"))
})
})
})
r.With(middleware.EnforceSetup()).Route("/hosts", resourceRoutes{
idParam: "hostID",
model: host.Model{},
viewCapability: user.CapabilityHostsView,
manageCapability: user.CapabilityHostsManage,
createSchema: schema.CreateHost(),
updateSchema: schema.UpdateHost(),
list: handler.GetHosts(),
create: handler.CreateHost(),
get: handler.GetHost(),
update: handler.UpdateHost(),
del: handler.DeleteHost(),
manageItem: func(r chi.Router) {
r.Get("/nginx-config", handler.GetHostNginxConfig("json"))
r.Get("/nginx-config.txt", handler.GetHostNginxConfig("text"))
},
}.mount)
// Nginx Templates
r.With(middleware.EnforceSetup()).Route("/nginx-templates", func(r chi.Router) {
// List
r.With(
middleware.Enforce(user.CapabilityNginxTemplatesView),
middleware.ListQuery(nginxtemplate.Model{}),
).Get("/", handler.GetNginxTemplates())
// Create
r.With(middleware.Enforce(user.CapabilityNginxTemplatesManage), middleware.EnforceRequestSchema(schema.CreateNginxTemplate())).
Post("/", handler.CreateNginxTemplate())
// Specific Item
r.Route("/{templateID:[0-9]+}", func(r chi.Router) {
r.With(middleware.Enforce(user.CapabilityNginxTemplatesView)).
Get("/", handler.GetNginxTemplates())
r.With(middleware.Enforce(user.CapabilityHostsManage)).Route("/", func(r chi.Router) {
r.Delete("/", handler.DeleteNginxTemplate())
r.With(middleware.EnforceRequestSchema(schema.UpdateNginxTemplate())).
Put("/", handler.UpdateNginxTemplate())
})
})
})
r.With(middleware.EnforceSetup()).Route("/nginx-templates", resourceRoutes{
idParam: "templateID",
model: nginxtemplate.Model{},
viewCapability: user.CapabilityNginxTemplatesView,
manageCapability: user.CapabilityNginxTemplatesManage,
createSchema: schema.CreateNginxTemplate(),
updateSchema: schema.UpdateNginxTemplate(),
list: handler.GetNginxTemplates(),
create: handler.CreateNginxTemplate(),
get: handler.GetNginxTemplate(),
update: handler.UpdateNginxTemplate(),
del: handler.DeleteNginxTemplate(),
}.mount)
// Streams
r.With(middleware.EnforceSetup()).Route("/streams", func(r chi.Router) {
// List
r.With(
middleware.Enforce(user.CapabilityStreamsView),
middleware.ListQuery(stream.Model{}),
).Get("/", handler.GetStreams())
// Create
r.With(middleware.Enforce(user.CapabilityStreamsManage), middleware.EnforceRequestSchema(schema.CreateStream())).
Post("/", handler.CreateStream())
// Specific Item
r.Route("/{hostID:[0-9]+}", func(r chi.Router) {
r.With(middleware.Enforce(user.CapabilityStreamsView)).
Get("/", handler.GetStream())
r.With(middleware.Enforce(user.CapabilityHostsManage)).Route("/", func(r chi.Router) {
r.Delete("/", handler.DeleteStream())
r.With(middleware.EnforceRequestSchema(schema.UpdateStream())).
Put("/", handler.UpdateStream())
})
})
})
r.With(middleware.EnforceSetup()).Route("/streams", resourceRoutes{
idParam: "hostID",
model: stream.Model{},
viewCapability: user.CapabilityStreamsView,
manageCapability: user.CapabilityStreamsManage,
createSchema: schema.CreateStream(),
updateSchema: schema.UpdateStream(),
list: handler.GetStreams(),
create: handler.CreateStream(),
get: handler.GetStream(),
update: handler.UpdateStream(),
del: handler.DeleteStream(),
}.mount)
// Upstreams
r.With(middleware.EnforceSetup()).Route("/upstreams", func(r chi.Router) {
// List
r.With(
middleware.Enforce(user.CapabilityHostsView),
middleware.ListQuery(upstream.Model{}),
).Get("/", handler.GetUpstreams())
// Create
r.With(middleware.Enforce(user.CapabilityHostsManage), middleware.EnforceRequestSchema(schema.CreateUpstream())).
Post("/", handler.CreateUpstream())
// Specific Item
r.Route("/{upstreamID:[0-9]+}", func(r chi.Router) {
r.With(middleware.Enforce(user.CapabilityHostsView)).
Get("/", handler.GetUpstream())
r.With(middleware.Enforce(user.CapabilityHostsManage)).Route("/", func(r chi.Router) {
r.Delete("/", handler.DeleteUpstream())
r.With(middleware.EnforceRequestSchema(schema.UpdateUpstream())).
Put("/", handler.UpdateUpstream())
r.Get("/nginx-config", handler.GetUpstreamNginxConfig("json"))
r.Get("/nginx-config.txt", handler.GetUpstreamNginxConfig("text"))
})
})
})
r.With(middleware.EnforceSetup()).Route("/upstreams", resourceRoutes{
idParam: "upstreamID",
model: upstream.Model{},
viewCapability: user.CapabilityHostsView,
manageCapability: user.CapabilityHostsManage,
createSchema: schema.CreateUpstream(),
updateSchema: schema.UpdateUpstream(),
list: handler.GetUpstreams(),
create: handler.CreateUpstream(),
get: handler.GetUpstream(),
update: handler.UpdateUpstream(),
del: handler.DeleteUpstream(),
manageItem: func(r chi.Router) {
r.Get("/nginx-config", handler.GetUpstreamNginxConfig("json"))
r.Get("/nginx-config.txt", handler.GetUpstreamNginxConfig("text"))
},
}.mount)
})
return r
+1 -3
View File
@@ -3,7 +3,6 @@ package api
import (
"net/http"
"net/http/httptest"
"os"
"testing"
"npm/internal/config"
@@ -21,8 +20,7 @@ var (
// Tear up/down
func TestMain(m *testing.M) {
config.Init(&version, &commit)
code := m.Run()
os.Exit(code)
m.Run()
}
func TestGetHealthz(t *testing.T) {
+17
View File
@@ -6,6 +6,23 @@ import (
"npm/internal/errors"
)
// JSON schema types and common field titles used by providers
const (
typeString = "string"
typeObject = "object"
typeInteger = "integer"
typeBoolean = "boolean"
titleAPIKey = "api-key"
titleAPIURL = "api-url"
titleKey = "key"
titlePassword = "password"
titleSecret = "secret"
titleToken = "token"
titleUser = "user"
titleUsername = "username"
)
// providerField should mimick jsonschema, so that
// the ui can render a field and validate it
// before we do.
+7 -7
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSAcmeDNS() Provider {
return Provider{
Title: "dns_acmedns",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"ACMEDNS_BASE_URL",
@@ -14,19 +14,19 @@ func getDNSAcmeDNS() Provider {
Properties: map[string]providerField{
"ACMEDNS_BASE_URL": {
Title: "base-url",
Type: "string",
Type: typeString,
},
"ACMEDNS_SUBDOMAIN": {
Title: "subdomain",
Type: "string",
Type: typeString,
},
"ACMEDNS_USERNAME": {
Title: "username",
Type: "string",
Title: titleUsername,
Type: typeString,
},
"ACMEDNS_PASSWORD": {
Title: "password",
Type: "string",
Title: titlePassword,
Type: typeString,
IsSecret: true,
},
},
+3 -3
View File
@@ -3,15 +3,15 @@ package dnsproviders
func getDNSAd() Provider {
return Provider{
Title: "dns_ad",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"AD_API_KEY",
},
Properties: map[string]providerField{
"AD_API_KEY": {
Title: "api-key",
Type: "string",
Title: titleAPIKey,
Type: typeString,
MinLength: 1,
},
},
+5 -5
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSAli() Provider {
return Provider{
Title: "dns_ali",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"Ali_Key",
@@ -11,13 +11,13 @@ func getDNSAli() Provider {
},
Properties: map[string]providerField{
"Ali_Key": {
Title: "api-key",
Type: "string",
Title: titleAPIKey,
Type: typeString,
MinLength: 1,
},
"Ali_Secret": {
Title: "secret",
Type: "string",
Title: titleSecret,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+6 -6
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSAutoDNS() Provider {
return Provider{
Title: "dns_autodns",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"AUTODNS_USER",
@@ -12,19 +12,19 @@ func getDNSAutoDNS() Provider {
},
Properties: map[string]providerField{
"AUTODNS_USER": {
Title: "user",
Type: "string",
Title: titleUser,
Type: typeString,
MinLength: 1,
},
"AUTODNS_PASSWORD": {
Title: "password",
Type: "string",
Title: titlePassword,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
"AUTODNS_CONTEXT": {
Title: "context",
Type: "string",
Type: typeString,
MinLength: 1,
},
},
+4 -4
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSAws() Provider {
return Provider{
Title: "dns_aws",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"AWS_ACCESS_KEY_ID",
@@ -12,18 +12,18 @@ func getDNSAws() Provider {
Properties: map[string]providerField{
"AWS_ACCESS_KEY_ID": {
Title: "access-key-id",
Type: "string",
Type: typeString,
MinLength: 10,
},
"AWS_SECRET_ACCESS_KEY": {
Title: "secret-access-key",
Type: "string",
Type: typeString,
MinLength: 10,
IsSecret: true,
},
"AWS_DNS_SLOWRATE": {
Title: "slow-rate",
Type: "integer",
Type: typeInteger,
},
},
}
+5 -5
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSAzure() Provider {
return Provider{
Title: "dns_azure",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"AZUREDNS_SUBSCRIPTIONID",
@@ -14,22 +14,22 @@ func getDNSAzure() Provider {
Properties: map[string]providerField{
"AZUREDNS_SUBSCRIPTIONID": {
Title: "subscription-id",
Type: "string",
Type: typeString,
MinLength: 1,
},
"AZUREDNS_TENANTID": {
Title: "tenant-id",
Type: "string",
Type: typeString,
MinLength: 1,
},
"AZUREDNS_APPID": {
Title: "app-id",
Type: "string",
Type: typeString,
MinLength: 1,
},
"AZUREDNS_CLIENTSECRET": {
Title: "client-secret",
Type: "string",
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+8 -8
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSCf() Provider {
return Provider{
Title: "dns_cf",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"CF_Key",
@@ -13,29 +13,29 @@ func getDNSCf() Provider {
},
Properties: map[string]providerField{
"CF_Key": {
Title: "api-key",
Type: "string",
Title: titleAPIKey,
Type: typeString,
MinLength: 1,
},
"CF_Email": {
Title: "email",
Type: "string",
Type: typeString,
MinLength: 5,
},
"CF_Token": {
Title: "token",
Type: "string",
Title: titleToken,
Type: typeString,
MinLength: 5,
IsSecret: true,
},
"CF_Account_ID": {
Title: "account-id",
Type: "string",
Type: typeString,
MinLength: 1,
},
"CF_Zone_ID": {
Title: "zone-id",
Type: "string",
Type: typeString,
MinLength: 1,
},
},
+5 -5
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSCloudns() Provider {
return Provider{
Title: "dns_cloudns",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"CLOUDNS_AUTH_ID",
@@ -13,17 +13,17 @@ func getDNSCloudns() Provider {
Properties: map[string]providerField{
"CLOUDNS_AUTH_ID": {
Title: "auth-id",
Type: "string",
Type: typeString,
MinLength: 1,
},
"CLOUDNS_SUB_AUTH_ID": {
Title: "sub-auth-id",
Type: "string",
Type: typeString,
MinLength: 1,
},
"CLOUDNS_AUTH_PASSWORD": {
Title: "password",
Type: "string",
Title: titlePassword,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+8 -8
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSConoha() Provider {
return Provider{
Title: "dns_conoha",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"CONOHA_IdentityServiceApi",
@@ -13,24 +13,24 @@ func getDNSConoha() Provider {
},
Properties: map[string]providerField{
"CONOHA_IdentityServiceApi": {
Title: "api-url",
Type: "string",
Title: titleAPIURL,
Type: typeString,
MinLength: 4,
},
"CONOHA_Username": {
Title: "username",
Type: "string",
Title: titleUsername,
Type: typeString,
MinLength: 1,
},
"CONOHA_Password": {
Title: "password",
Type: "string",
Title: titlePassword,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
"CONOHA_TenantId": {
Title: "tenant-id",
Type: "string",
Type: typeString,
MinLength: 1,
},
},
+5 -5
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSCx() Provider {
return Provider{
Title: "dns_cx",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"CX_Key",
@@ -11,12 +11,12 @@ func getDNSCx() Provider {
},
Properties: map[string]providerField{
"CX_Key": {
Title: "key",
Type: "string",
Title: titleKey,
Type: typeString,
},
"CX_Secret": {
Title: "secret",
Type: "string",
Title: titleSecret,
Type: typeString,
IsSecret: true,
},
},
+6 -6
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSCyon() Provider {
return Provider{
Title: "dns_cyon",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"CY_Username",
@@ -12,19 +12,19 @@ func getDNSCyon() Provider {
},
Properties: map[string]providerField{
"CY_Username": {
Title: "user",
Type: "string",
Title: titleUser,
Type: typeString,
MinLength: 1,
},
"CY_Password": {
Title: "password",
Type: "string",
Title: titlePassword,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
"CY_OTP_Secret": {
Title: "otp-secret",
Type: "string",
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+4 -4
View File
@@ -3,20 +3,20 @@ package dnsproviders
func getDNSDa() Provider {
return Provider{
Title: "dns_da",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"DA_Api",
},
Properties: map[string]providerField{
"DA_Api": {
Title: "api-url",
Type: "string",
Title: titleAPIURL,
Type: typeString,
MinLength: 4,
},
"DA_Api_Insecure": {
Title: "insecure",
Type: "boolean",
Type: typeBoolean,
},
},
}
+3 -3
View File
@@ -3,15 +3,15 @@ package dnsproviders
func getDNSDgon() Provider {
return Provider{
Title: "dns_dgon",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"DO_API_KEY",
},
Properties: map[string]providerField{
"DO_API_KEY": {
Title: "api-key",
Type: "string",
Title: titleAPIKey,
Type: typeString,
IsSecret: true,
},
},
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSDNSimple() Provider {
return Provider{
Title: "dns_dnsimple",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"DNSimple_OAUTH_TOKEN",
@@ -11,7 +11,7 @@ func getDNSDNSimple() Provider {
Properties: map[string]providerField{
"DNSimple_OAUTH_TOKEN": {
Title: "oauth-token",
Type: "string",
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+4 -4
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSDp() Provider {
return Provider{
Title: "dns_dp",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"DP_Id",
@@ -12,12 +12,12 @@ func getDNSDp() Provider {
Properties: map[string]providerField{
"DP_Id": {
Title: "id",
Type: "string",
Type: typeString,
MinLength: 1,
},
"DP_Key": {
Title: "key",
Type: "string",
Title: titleKey,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+4 -4
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSDpi() Provider {
return Provider{
Title: "dns_dpi",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"DPI_Id",
@@ -12,12 +12,12 @@ func getDNSDpi() Provider {
Properties: map[string]providerField{
"DPI_Id": {
Title: "id",
Type: "string",
Type: typeString,
MinLength: 1,
},
"DPI_Key": {
Title: "key",
Type: "string",
Title: titleKey,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
@@ -3,15 +3,15 @@ package dnsproviders
func getDNSDreamhost() Provider {
return Provider{
Title: "dns_dreamhost",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"DH_API_KEY",
},
Properties: map[string]providerField{
"DH_API_KEY": {
Title: "api-key",
Type: "string",
Title: titleAPIKey,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+3 -3
View File
@@ -3,15 +3,15 @@ package dnsproviders
func getDNSDuckDNS() Provider {
return Provider{
Title: "dns_duckdns",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"DuckDNS_Token",
},
Properties: map[string]providerField{
"DuckDNS_Token": {
Title: "token",
Type: "string",
Title: titleToken,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+6 -6
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSDyn() Provider {
return Provider{
Title: "dns_dyn",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"DYN_Customer",
@@ -13,17 +13,17 @@ func getDNSDyn() Provider {
Properties: map[string]providerField{
"DYN_Customer": {
Title: "customer",
Type: "string",
Type: typeString,
MinLength: 1,
},
"DYN_Username": {
Title: "username",
Type: "string",
Title: titleUsername,
Type: typeString,
MinLength: 1,
},
"DYN_Password": {
Title: "password",
Type: "string",
Title: titlePassword,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+4 -4
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSDynu() Provider {
return Provider{
Title: "dns_dynu",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"Dynu_ClientId",
@@ -11,12 +11,12 @@ func getDNSDynu() Provider {
Properties: map[string]providerField{
"Dynu_ClientId": {
Title: "client-id",
Type: "string",
Type: typeString,
MinLength: 1,
},
"Dynu_Secret": {
Title: "secret",
Type: "string",
Title: titleSecret,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+5 -5
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSEuserv() Provider {
return Provider{
Title: "dns_euserv",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"EUSERV_Username",
@@ -11,13 +11,13 @@ func getDNSEuserv() Provider {
},
Properties: map[string]providerField{
"EUSERV_Username": {
Title: "user",
Type: "string",
Title: titleUser,
Type: typeString,
MinLength: 1,
},
"EUSERV_Password": {
Title: "password",
Type: "string",
Title: titlePassword,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+5 -5
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSFreeDNS() Provider {
return Provider{
Title: "dns_freedns",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"FREEDNS_User",
@@ -11,13 +11,13 @@ func getDNSFreeDNS() Provider {
},
Properties: map[string]providerField{
"FREEDNS_User": {
Title: "user",
Type: "string",
Title: titleUser,
Type: typeString,
MinLength: 1,
},
"FREEDNS_Password": {
Title: "password",
Type: "string",
Title: titlePassword,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
@@ -3,15 +3,15 @@ package dnsproviders
func getDNSGandiLiveDNS() Provider {
return Provider{
Title: "dns_gandi_livedns",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"GANDI_LIVEDNS_KEY",
},
Properties: map[string]providerField{
"GANDI_LIVEDNS_KEY": {
Title: "key",
Type: "string",
Title: titleKey,
Type: typeString,
MinLength: 1,
},
},
+5 -5
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSGd() Provider {
return Provider{
Title: "dns_gd",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"GD_Key",
@@ -11,13 +11,13 @@ func getDNSGd() Provider {
},
Properties: map[string]providerField{
"GD_Key": {
Title: "key",
Type: "string",
Title: titleKey,
Type: typeString,
MinLength: 1,
},
"GD_Secret": {
Title: "secret",
Type: "string",
Title: titleSecret,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+5 -5
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSHe() Provider {
return Provider{
Title: "dns_he",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"HE_Username",
@@ -11,13 +11,13 @@ func getDNSHe() Provider {
},
Properties: map[string]providerField{
"HE_Username": {
Title: "username",
Type: "string",
Title: titleUsername,
Type: typeString,
MinLength: 1,
},
"HE_Password": {
Title: "password",
Type: "string",
Title: titlePassword,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSInfoblox() Provider {
return Provider{
Title: "dns_infoblox",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"Infoblox_Creds",
@@ -12,13 +12,13 @@ func getDNSInfoblox() Provider {
Properties: map[string]providerField{
"Infoblox_Creds": {
Title: "credentials",
Type: "string",
Type: typeString,
MinLength: 1,
IsSecret: true,
},
"Infoblox_Server": {
Title: "server",
Type: "string",
Type: typeString,
MinLength: 1,
},
},
+5 -5
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSInwx() Provider {
return Provider{
Title: "dns_inwx",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"INWX_User",
@@ -11,13 +11,13 @@ func getDNSInwx() Provider {
},
Properties: map[string]providerField{
"INWX_User": {
Title: "user",
Type: "string",
Title: titleUser,
Type: typeString,
MinLength: 1,
},
"INWX_Password": {
Title: "password",
Type: "string",
Title: titlePassword,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSIspconfig() Provider {
return Provider{
Title: "dns_ispconfig",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"ISPC_User",
@@ -12,24 +12,24 @@ func getDNSIspconfig() Provider {
},
Properties: map[string]providerField{
"ISPC_User": {
Title: "user",
Type: "string",
Title: titleUser,
Type: typeString,
MinLength: 1,
},
"ISPC_Password": {
Title: "password",
Type: "string",
Title: titlePassword,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
"ISPC_Api": {
Title: "api-url",
Type: "string",
Title: titleAPIURL,
Type: typeString,
MinLength: 1,
},
"ISPC_Api_Insecure": {
Title: "insecure",
Type: "boolean",
Type: typeBoolean,
},
},
}
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSKinghost() Provider {
return Provider{
Title: "dns_kinghost",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"KINGHOST_Username",
@@ -11,13 +11,13 @@ func getDNSKinghost() Provider {
},
Properties: map[string]providerField{
"KINGHOST_Username": {
Title: "user",
Type: "string",
Title: titleUser,
Type: typeString,
MinLength: 1,
},
"KINGHOST_Password": {
Title: "password",
Type: "string",
Title: titlePassword,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
@@ -5,15 +5,15 @@ package dnsproviders
func getDNSLinodeV4() Provider {
return Provider{
Title: "dns_linode_v4",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"LINODE_V4_API_KEY",
},
Properties: map[string]providerField{
"LINODE_V4_API_KEY": {
Title: "api-key",
Type: "string",
Title: titleAPIKey,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+7 -7
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSLoopia() Provider {
return Provider{
Title: "dns_loopia",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"LOOPIA_Api",
@@ -12,18 +12,18 @@ func getDNSLoopia() Provider {
},
Properties: map[string]providerField{
"LOOPIA_Api": {
Title: "api-url",
Type: "string",
Title: titleAPIURL,
Type: typeString,
MinLength: 4,
},
"LOOPIA_User": {
Title: "user",
Type: "string",
Title: titleUser,
Type: typeString,
MinLength: 1,
},
"LOOPIA_Password": {
Title: "password",
Type: "string",
Title: titlePassword,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+4 -4
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSLua() Provider {
return Provider{
Title: "dns_lua",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"LUA_Key",
@@ -11,14 +11,14 @@ func getDNSLua() Provider {
},
Properties: map[string]providerField{
"LUA_Key": {
Title: "key",
Type: "string",
Title: titleKey,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
"LUA_Email": {
Title: "email",
Type: "string",
Type: typeString,
MinLength: 5,
},
},
+5 -5
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSMe() Provider {
return Provider{
Title: "dns_me",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"ME_Key",
@@ -11,13 +11,13 @@ func getDNSMe() Provider {
},
Properties: map[string]providerField{
"ME_Key": {
Title: "key",
Type: "string",
Title: titleKey,
Type: typeString,
MinLength: 1,
},
"ME_Secret": {
Title: "secret",
Type: "string",
Title: titleSecret,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+5 -5
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSNamecom() Provider {
return Provider{
Title: "dns_namecom",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"Namecom_Username",
@@ -11,13 +11,13 @@ func getDNSNamecom() Provider {
},
Properties: map[string]providerField{
"Namecom_Username": {
Title: "username",
Type: "string",
Title: titleUsername,
Type: typeString,
MinLength: 1,
},
"Namecom_Token": {
Title: "token",
Type: "string",
Title: titleToken,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
@@ -3,15 +3,15 @@ package dnsproviders
func getDNSNamesilo() Provider {
return Provider{
Title: "dns_namesilo",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"Namesilo_Key",
},
Properties: map[string]providerField{
"Namesilo_Key": {
Title: "api-key",
Type: "string",
Title: titleAPIKey,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+3 -3
View File
@@ -3,15 +3,15 @@ package dnsproviders
func getDNSOne() Provider {
return Provider{
Title: "dns_nsone",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"NS1_Key",
},
Properties: map[string]providerField{
"NS1_Key": {
Title: "key",
Type: "string",
Title: titleKey,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+6 -6
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSPDNS() Provider {
return Provider{
Title: "dns_pdns",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"PDNS_Url",
@@ -14,22 +14,22 @@ func getDNSPDNS() Provider {
Properties: map[string]providerField{
"PDNS_Url": {
Title: "url",
Type: "string",
Type: typeString,
MinLength: 1,
},
"PDNS_ServerId": {
Title: "server-id",
Type: "string",
Type: typeString,
MinLength: 1,
},
"PDNS_Token": {
Title: "token",
Type: "string",
Title: titleToken,
Type: typeString,
MinLength: 1,
},
"PDNS_Ttl": {
Title: "ttl",
Type: "integer",
Type: typeInteger,
Minimum: 1,
},
},
@@ -3,15 +3,15 @@ package dnsproviders
func getDNSSelectel() Provider {
return Provider{
Title: "dns_selectel",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"SL_Key",
},
Properties: map[string]providerField{
"SL_Key": {
Title: "api-key",
Type: "string",
Title: titleAPIKey,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSServercow() Provider {
return Provider{
Title: "dns_servercow",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"SERVERCOW_API_Username",
@@ -11,13 +11,13 @@ func getDNSServercow() Provider {
},
Properties: map[string]providerField{
"SERVERCOW_API_Username": {
Title: "user",
Type: "string",
Title: titleUser,
Type: typeString,
MinLength: 1,
},
"SERVERCOW_API_Password": {
Title: "password",
Type: "string",
Title: titlePassword,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+5 -5
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSTele3() Provider {
return Provider{
Title: "dns_tele3",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"TELE3_Key",
@@ -11,13 +11,13 @@ func getDNSTele3() Provider {
},
Properties: map[string]providerField{
"TELE3_Key": {
Title: "key",
Type: "string",
Title: titleKey,
Type: typeString,
MinLength: 1,
},
"TELE3_Secret": {
Title: "secret",
Type: "string",
Title: titleSecret,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+5 -5
View File
@@ -3,7 +3,7 @@ package dnsproviders
func getDNSUnoeuro() Provider {
return Provider{
Title: "dns_unoeuro",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"UNO_Key",
@@ -11,14 +11,14 @@ func getDNSUnoeuro() Provider {
},
Properties: map[string]providerField{
"UNO_Key": {
Title: "key",
Type: "string",
Title: titleKey,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
"UNO_User": {
Title: "user",
Type: "string",
Title: titleUser,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+3 -3
View File
@@ -3,15 +3,15 @@ package dnsproviders
func getDNSVscale() Provider {
return Provider{
Title: "dns_vscale",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"VSCALE_API_KEY",
},
Properties: map[string]providerField{
"VSCALE_API_KEY": {
Title: "api-key",
Type: "string",
Title: titleAPIKey,
Type: typeString,
MinLength: 1,
},
},
+3 -3
View File
@@ -3,15 +3,15 @@ package dnsproviders
func getDNSYandex() Provider {
return Provider{
Title: "dns_yandex",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"PDD_Token",
},
Properties: map[string]providerField{
"PDD_Token": {
Title: "token",
Type: "string",
Title: titleToken,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+3 -3
View File
@@ -3,15 +3,15 @@ package dnsproviders
func getDNSDNZilore() Provider {
return Provider{
Title: "dns_zilore",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"Zilore_Key",
},
Properties: map[string]providerField{
"Zilore_Key": {
Title: "api-key",
Type: "string",
Title: titleAPIKey,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
+3 -3
View File
@@ -3,15 +3,15 @@ package dnsproviders
func getDNSZonomi() Provider {
return Provider{
Title: "dns_zonomi",
Type: "object",
Type: typeObject,
AdditionalProperties: false,
Required: []string{
"ZM_Key",
},
Properties: map[string]providerField{
"ZM_Key": {
Title: "api-key",
Type: "string",
Title: titleAPIKey,
Type: typeString,
MinLength: 1,
IsSecret: true,
},
@@ -31,6 +31,11 @@ func (m *Model) LoadByID(id uint) error {
return result.Error
}
// SetUserID sets the owner of this model
func (m *Model) SetUserID(userID uint) {
m.UserID = userID
}
// Save will save this model to the DB
func (m *Model) Save() error {
if m.UserID == 0 {
+2 -1
View File
@@ -41,7 +41,8 @@ type OAuthUser struct {
func (m *OAuthUser) GetResourceField(field string) string {
if m.Resource != nil {
if value, ok := m.Resource[field]; ok {
return value.(string)
str, _ := value.(string)
return str
}
}
return ""
@@ -210,9 +210,7 @@ func (s *testsuite) TestDelete() {
assert.Equal(s.T(), "Unable to delete a new object", err.Error())
m2 := Model{
Base: model.Base{
ID: 10,
},
ID: 10,
}
err2 := m2.Delete()
require.NoError(s.T(), err2)
@@ -296,9 +296,7 @@ func (s *testsuite) TestDelete() {
assert.Equal(s.T(), "Unable to delete a new object", err.Error())
m2 := Model{
Base: model.Base{
ID: 10,
},
ID: 10,
}
err2 := m2.Delete()
require.NoError(s.T(), err2)
@@ -34,6 +34,11 @@ func (m *Model) LoadByID(id uint) error {
return result.Error
}
// SetUserID sets the owner of this model
func (m *Model) SetUserID(userID uint) {
m.UserID = userID
}
// Save will save this model to the DB
func (m *Model) Save() error {
if m.UserID == 0 {
+4 -9
View File
@@ -5,7 +5,6 @@ import (
"testing"
"time"
"npm/internal/model"
"npm/internal/status"
"npm/internal/test"
"npm/internal/types"
@@ -189,9 +188,7 @@ func (s *testsuite) TestDelete() {
assert.Equal(s.T(), "Unable to delete a new object", err.Error())
m2 := Model{
Base: model.Base{
ID: 10,
},
ID: 10,
}
err2 := m2.Delete()
require.NoError(s.T(), err2)
@@ -203,11 +200,9 @@ func (s *testsuite) TestGetTemplate() {
defer goleak.VerifyNone(s.T(), goleak.IgnoreAnyFunction("database/sql.(*DB).connectionOpener"))
m := Model{
Base: model.Base{
ID: 10,
CreatedAt: time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC).UnixMilli(),
UpdatedAt: time.Date(2018, 8, 12, 7, 30, 24, 16, time.UTC).UnixMilli(),
},
ID: 10,
CreatedAt: time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC).UnixMilli(),
UpdatedAt: time.Date(2018, 8, 12, 7, 30, 24, 16, time.UTC).UnixMilli(),
UserID: 100,
Type: "proxy",
NginxTemplateID: 20,
+1 -3
View File
@@ -23,9 +23,7 @@ func ListQueryBuilder(
filters []model.Filter,
filterMap map[string]model.FilterMapValue,
) *gorm.DB {
scopes := make([]func(*gorm.DB) *gorm.DB, 0)
scopes = append(scopes, ScopeFilters(filters, filterMap))
return database.GetDB().Scopes(scopes...)
return database.GetDB().Scopes(ScopeFilters(filters, filterMap))
}
// AddOrderToList is used after query above is used for counting
@@ -28,6 +28,11 @@ func (m *Model) LoadByID(id uint) error {
return result.Error
}
// SetUserID sets the owner of this model
func (m *Model) SetUserID(userID uint) {
m.UserID = userID
}
// Save will save this model to the DB
func (m *Model) Save() error {
if m.UserID == 0 {
+2 -12
View File
@@ -96,7 +96,7 @@ func ScopeFilters(filters []model.Filter, filterMap map[string]model.FilterMapVa
}
func sortToOrderString(sorts []model.Sort) string {
strs := make([]string, 0)
strs := make([]string, 0, len(sorts))
for _, i := range sorts {
str := i.Field
if i.Direction != "" {
@@ -110,17 +110,7 @@ func sortToOrderString(sorts []model.Sort) string {
func parseBoolValue(v string) []string {
bVal := "0"
switch strings.ToLower(v) {
case "yes":
fallthrough
case "true":
fallthrough
case "on":
fallthrough
case "t":
fallthrough
case "1":
fallthrough
case "y":
case "yes", "true", "on", "t", "1", "y":
bVal = "1"
}
return []string{bVal}
+5
View File
@@ -31,6 +31,11 @@ func (m *Model) LoadByID(id uint) error {
return result.Error
}
// SetUserID sets the owner of this model
func (m *Model) SetUserID(userID uint) {
m.UserID = userID
}
// Save will save this model to the DB
func (m *Model) Save() error {
if m.UserID == 0 {
+2 -6
View File
@@ -226,9 +226,7 @@ func (s *testsuite) TestDelete() {
assert.Equal(s.T(), "Unable to delete a new object", err.Error())
m2 := Model{
Base: model.Base{
ID: 10,
},
ID: 10,
Name: "John Doe",
}
err2 := m2.Delete()
@@ -442,9 +440,7 @@ func (s *testsuite) TestSaveCapabilitiesInvalid() {
// Empty model returns error
m := Model{
Base: model.Base{
ID: 10,
},
ID: 10,
Capabilities: []string{"doesnotexist", "hosts.manage"},
}
err := m.SaveCapabilities()
+5 -5
View File
@@ -89,16 +89,16 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ent
// DeleteAll will do just that, and should only be used for testing purposes.
func DeleteAll() error {
db := database.GetDB()
if result := db.Exec(
result := db.Exec(
fmt.Sprintf(`DELETE FROM %s WHERE is_system = ?`, database.QuoteTableName("user")),
false,
); result.Error != nil {
)
if result.Error != nil {
return result.Error
}
if result := db.Exec(
fmt.Sprintf(`DELETE FROM %s`, database.QuoteTableName("auth")),
); result.Error != nil {
result = db.Exec(fmt.Sprintf(`DELETE FROM %s`, database.QuoteTableName("auth")))
if result.Error != nil {
return result.Error
}
+8 -11
View File
@@ -33,17 +33,14 @@ func TestShutdown(t *testing.T) {
err := Shutdown()
require.Nil(t, err, "Shutdown should not return an error when jobqueue is started")
// nolint: gosimple
select {
case <-ctx.Done():
switch ctx.Err() {
case context.DeadlineExceeded:
fmt.Println("context timeout exceeded")
case context.Canceled:
fmt.Println("context cancelled by force. whole process is complete")
default:
require.Nil(t, ctx.Err(), "Context done state has unexpected value")
}
<-ctx.Done()
switch ctx.Err() {
case context.DeadlineExceeded:
fmt.Println("context timeout exceeded")
case context.Canceled:
fmt.Println("context cancelled by force. whole process is complete")
default:
require.Nil(t, ctx.Err(), "Context done state has unexpected value")
}
require.Nil(t, cancel, "Cancel function should be nil after Shutdown")
+4 -13
View File
@@ -5,7 +5,6 @@ import (
"testing"
"npm/internal/entity/user"
"npm/internal/model"
"npm/internal/test"
"github.com/DATA-DOG/go-sqlmock"
@@ -183,9 +182,7 @@ func (s *testsuite) TestGetPrivateKey() {
// Set currentKeys and try again
currentKeys = KeysModel{
Base: model.Base{
ID: 10,
},
ID: 10,
PrivateKey: s.privateKeyString,
PublicKey: s.publicKeyString,
}
@@ -210,9 +207,7 @@ func (s *testsuite) TestGetPublicKey() {
// Set currentKeys and try again
currentKeys = KeysModel{
Base: model.Base{
ID: 10,
},
ID: 10,
PrivateKey: s.privateKeyString,
PublicKey: s.publicKeyString,
}
@@ -228,17 +223,13 @@ func (s *testsuite) TestGenerate() {
defer goleak.VerifyNone(s.T(), goleak.IgnoreAnyFunction("database/sql.(*DB).connectionOpener"))
currentKeys = KeysModel{
Base: model.Base{
ID: 10,
},
ID: 10,
PrivateKey: s.privateKeyString,
PublicKey: s.publicKeyString,
}
usr := user.Model{
Base: model.Base{
ID: 10,
},
ID: 10,
}
// test 1, user key
-1
View File
@@ -168,7 +168,6 @@ func TestConfigure(t *testing.T) {
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
if err := Configure(tt.args.c); (err != nil) != tt.wantErr {
t.Errorf("Configure() error = %v, wantErr %v", err, tt.wantErr)
-347
View File
@@ -1,347 +0,0 @@
package migrations
import (
"time"
"npm/internal/entity"
"npm/internal/entity/certificateauthority"
"npm/internal/entity/nginxtemplate"
"npm/internal/entity/setting"
"npm/internal/entity/user"
"npm/internal/model"
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/datatypes"
"gorm.io/gorm"
)
const proxyTemplate = `# ------------------------------------------------------------
{{#each Host.DomainNames}}
# {{this}}
{{/each}}
# ------------------------------------------------------------
server {
{{#if Config.Ipv4}}
listen 80;
{{/if}}
{{#if Config.Ipv6}}
listen [::]:80;
{{/if}}
{{#if Certificate.ID}}
{{#if Config.Ipv4}}
listen 443 ssl {{#if Host.HTTP2Support}}http2{{/if}};
{{/if}}
{{#if Config.Ipv6}}
listen [::]:443 ssl {{#if Host.HTTP2Support}}http2{{/if}};
{{/if}}
{{/if}}
server_name {{#each Host.DomainNames}}{{this}} {{/each}};
{{#if Certificate.ID}}
include conf.d/include/ssl-ciphers.conf;
{{#if Certificate.IsAcme}}
ssl_certificate {{Certificate.Folder}}/fullchain.pem;
ssl_certificate_key {{Certificate.Folder}}/privkey.pem;
{{else}}
# Custom SSL
ssl_certificate /data/custom_ssl/npm-{{Certicicate.ID}}/fullchain.pem;
ssl_certificate_key /data/custom_ssl/npm-{{Certificate.ID}}/privkey.pem;
{{/if}}
{{/if}}
{{#if Host.CachingEnabled}}
include conf.d/include/assets.conf;
{{/if}}
{{#if Host.BlockExploits}}
include conf.d/include/block-exploits.conf;
{{/if}}
{{#if Certificate.ID}}
{{#if Host.SSLForced}}
{{#if Host.HSTSEnabled}}
# HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years)
add_header Strict-Transport-Security "max-age=63072000;{{#if Host.HSTSSubdomains}} includeSubDomains;{{/if}} preload" always;
{{/if}}
# Force SSL
include conf.d/include/force-ssl.conf;
{{/if}}
{{/if}}
{{#if Host.AllowWebsocketUpgrade}}
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_http_version 1.1;
{{/if}}
access_log /data/logs/host-{{Host.ID}}_access.log proxy;
error_log /data/logs/host-{{Host.ID}}_error.log warn;
{{Host.AdvancedConfig}}
# locations ?
# default location:
location / {
{{#if Host.AccessListID}}
# Authorization
auth_basic "Authorization required";
auth_basic_user_file /data/access/{{Host.AccessListID}};
# access_list.passauth ? todo
{{/if}}
# Access Rules ? todo
# Access checks must...? todo
{{#if Certificate.ID}}
{{#if Host.SSLForced}}
{{#if Host.HSTSEnabled}}
# HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years)
add_header Strict-Transport-Security "max-age=63072000;{{#if Host.HSTSSubdomains}} includeSubDomains;{{/if}} preload" always;
{{/if}}
{{/if}}
{{/if}}
{{#if Host.AllowWebsocketUpgrade}}
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
{{/if}}
# Proxy!
add_header X-Served-By $host;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_http_version 1.1;
{{#if Upstream.ID}}
# upstream
proxy_pass {{Host.ProxyScheme}}://npm_upstream_{{Upstream.ID}};
{{else}}
# proxy a single host
proxy_pass {{Host.ProxyScheme}}://{{Host.ProxyHost}}:{{Host.ProxyPort}};
{{/if}}
}
# Legacy Custom Configuration
include /data/nginx/custom/server_proxy[.]conf;
}
`
const upstreamTemplate = `# ------------------------------------------------------------
# Upstream {{Upstream.ID}}: {{Upstream.Name}}
# ------------------------------------------------------------
upstream npm_upstream_{{Upstream.ID}} {
{{#if Upstream.IPHash~}}
ip_hash;
{{~/if}}
{{#if Upstream.NTLM~}}
ntlm;
{{~/if}}
{{#if Upstream.Keepalive~}}
keepalive {{Upstream.Keepalive}};
{{~/if}}
{{#if Upstream.KeepaliveRequests~}}
keepalive_requests {{Upstream.KeepaliveRequests}};
{{~/if}}
{{#if Upstream.KeepaliveTime~}}
keepalive_time {{Upstream.KeepaliveTime}};
{{~/if}}
{{#if Upstream.KeepaliveTimeout~}}
keepalive_timeout {{Upstream.KeepaliveTimeout}};
{{~/if}}
{{Upstream.AdvancedConfig}}
{{#each Upstream.Servers~}}
{{#unless IsDeleted~}}
server {{Server}} {{#if Weight}}weight={{Weight}} {{/if}}{{#if MaxConns}}max_conns={{MaxConns}} {{/if}}{{#if MaxFails}}max_fails={{MaxFails}} {{/if}}{{#if FailTimeout}}fail_timeout={{FailTimeout}} {{/if}}{{#if Backup}}backup{{/if}};
{{/unless}}
{{/each}}
}
`
// initialData seeds the tables created by initialSchema with the same
// default rows that the old per-engine dbmate migrations inserted:
// capabilities, default settings, built-in certificate authorities, the
// system user, and the built-in nginx templates.
func initialData() *gormigrate.Migration {
return &gormigrate.Migration{
ID: "20201013035839",
Migrate: func(tx *gorm.DB) error {
now := time.Now().UnixMilli()
base := func() model.Base {
return model.Base{CreatedAt: now, UpdatedAt: now}
}
capabilities := []entity.Capability{
{Name: user.CapabilityFullAdmin},
{Name: user.CapabilityAccessListsView},
{Name: user.CapabilityAccessListsManage},
{Name: user.CapabilityAuditLogView},
{Name: user.CapabilityCertificatesView},
{Name: user.CapabilityCertificatesManage},
{Name: user.CapabilityCertificateAuthoritiesView},
{Name: user.CapabilityCertificateAuthoritiesManage},
{Name: user.CapabilityDNSProvidersView},
{Name: user.CapabilityDNSProvidersManage},
{Name: user.CapabilityHostsView},
{Name: user.CapabilityHostsManage},
{Name: user.CapabilityNginxTemplatesView},
{Name: user.CapabilityNginxTemplatesManage},
{Name: user.CapabilitySettingsManage},
{Name: user.CapabilityStreamsView},
{Name: user.CapabilityStreamsManage},
{Name: user.CapabilityUsersManage},
}
if err := tx.Create(&capabilities).Error; err != nil {
return err
}
settings := []setting.Model{
{
Base: base(),
Name: "default-site",
Description: "What to show users who hit your Nginx server by default",
Value: datatypes.JSON(`"welcome"`),
},
{
Base: base(),
Name: "auth-methods",
Description: "Which methods are enabled for authentication",
Value: datatypes.JSON(`["local"]`),
},
{
Base: base(),
Name: "oauth-auth",
Description: "Configuration for OAuth authentication",
Value: datatypes.JSON(`{}`),
},
{
Base: base(),
Name: "ldap-auth",
Description: "Configuration for LDAP authentication",
Value: datatypes.JSON(`{"host": "", "dn": "", "sync_by": "uid"}`),
},
}
if err := tx.Create(&settings).Error; err != nil {
return err
}
certificateAuthorities := []certificateauthority.Model{
{
Base: base(),
Name: "ZeroSSL",
AcmeshServer: "zerossl",
IsWildcardSupported: true,
MaxDomains: 10,
IsReadonly: true,
},
{
Base: base(),
Name: "Let's Encrypt",
AcmeshServer: "https://acme-v02.api.letsencrypt.org/directory",
IsWildcardSupported: true,
MaxDomains: 10,
IsReadonly: true,
},
{
Base: base(),
Name: "Buypass Go SSL",
AcmeshServer: "https://api.buypass.com/acme/directory",
IsWildcardSupported: false,
MaxDomains: 5,
IsReadonly: true,
},
{
Base: base(),
Name: "SSL.com",
AcmeshServer: "ssl.com",
IsWildcardSupported: false,
MaxDomains: 10,
IsReadonly: true,
},
{
Base: base(),
Name: "Let's Encrypt (Testing)",
AcmeshServer: "https://acme-staging-v02.api.letsencrypt.org/directory",
IsWildcardSupported: true,
MaxDomains: 10,
IsReadonly: true,
},
{
Base: base(),
Name: "Buypass Go SSL (Testing)",
AcmeshServer: "https://api.test4.buypass.no/acme/directory",
IsWildcardSupported: false,
MaxDomains: 5,
IsReadonly: true,
},
}
if err := tx.Create(&certificateAuthorities).Error; err != nil {
return err
}
systemUser := user.Model{
Base: base(),
Name: "System",
Email: "system@localhost",
IsSystem: true,
}
if err := tx.Create(&systemUser).Error; err != nil {
return err
}
templates := []nginxtemplate.Model{
{
Base: base(),
UserID: systemUser.ID,
Name: "Default Proxy Template",
Type: "proxy",
Template: proxyTemplate,
},
{
Base: base(),
UserID: systemUser.ID,
Name: "Default Redirect Template",
Type: "redirect",
Template: "# this is a redirect template",
},
{
Base: base(),
UserID: systemUser.ID,
Name: "Default Dead Template",
Type: "dead",
Template: "# this is a dead template",
},
{
Base: base(),
UserID: systemUser.ID,
Name: "Default Stream Template",
Type: "stream",
Template: "# this is a stream template",
},
{
Base: base(),
UserID: systemUser.ID,
Name: "Default Upstream Template",
Type: "upstream",
Template: upstreamTemplate,
},
}
return tx.Create(&templates).Error
},
}
}
+61
View File
@@ -0,0 +1,61 @@
package migrations
import (
"context"
"fmt"
"log/slog"
"slices"
"strings"
"npm/internal/logger"
)
// logHandler routes polyschema's slog output through the application
// logger, so migration progress and warnings (e.g. MySQL committing DDL
// implicitly) show up alongside everything else.
type logHandler struct {
attrs []slog.Attr
}
func newSlogLogger() *slog.Logger {
return slog.New(&logHandler{})
}
// Enabled lets every record through; the application logger applies its
// own level filtering.
func (*logHandler) Enabled(context.Context, slog.Level) bool {
return true
}
func (h *logHandler) Handle(_ context.Context, r slog.Record) error {
var b strings.Builder
b.WriteString(r.Message)
write := func(a slog.Attr) bool {
fmt.Fprintf(&b, " %s=%v", a.Key, a.Value)
return true
}
for _, a := range h.attrs {
write(a)
}
r.Attrs(write)
msg := b.String()
switch {
case r.Level >= slog.LevelWarn:
logger.Warn("%s", msg)
case r.Level >= slog.LevelInfo:
logger.Info("%s", msg)
default:
logger.Debug("%s", msg)
}
return nil
}
func (h *logHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return &logHandler{attrs: slices.Concat(h.attrs, attrs)}
}
// WithGroup is unused by polyschema, so groups are flattened.
func (h *logHandler) WithGroup(string) slog.Handler {
return h
}
+39 -70
View File
@@ -1,58 +1,49 @@
// Package migrations owns the application's database schema. It replaces
// the previous dbmate-based setup: instead of hand-written, per-engine SQL
// files, each migration is a small Go function driven by gorm, so a single
// migration works against Postgres, MySQL and SQLite alike.
// Package migrations owns the application's database schema and seed data.
//
// Both live as portable polyschema migration files in embed/migrations/
// (see https://github.com/jc21/polyschema): one YAML file per change,
// rendered into the right SQL for Postgres, MySQL or SQLite at runtime.
// Applied files are recorded in the historyTable, so on every boot only
// pending files run.
//
// Applied files must never be edited: polyschema checksums them and
// refuses to start if one changes. Schema changes are made by adding a
// new file with a higher version number.
package migrations
import (
"context"
"database/sql"
"fmt"
"io/fs"
"npm/embed"
"npm/internal/config"
"npm/internal/database"
"npm/internal/logger"
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
polyschema "github.com/jc21/polyschema/v1"
)
// legacyMigrationsTable is the table dbmate used to track applied
// migrations. Its presence indicates a database that was created before
// the switch to gormigrate.
const legacyMigrationsTable = "schema_migrations"
// legacyMigrationIDs maps the dbmate migration versions this application
// used to ship to the gormigrate migrations that replace them, in order.
// Existing installs already have this schema and data applied via dbmate,
// so on first boot after upgrading we mark them as already run rather
// than re-running them.
var legacyMigrationIDs = []string{
"20201013035318", // initial_schema
"20201013035839", // initial_data
}
// all is the ordered list of every migration this application has ever
// shipped. Entries must never be reordered or edited once released;
// schema changes are made by appending a new migration.
func all() []*gormigrate.Migration {
return []*gormigrate.Migration{
initialSchema(),
initialData(),
}
}
// historyTable tracks which migration files have been applied.
const historyTable = "migrations"
type afterMigrationComplete func()
// Migrate will bring the db up to date
// Migrate applies any pending migrations, then calls followup on success.
func Migrate(followup afterMigrationComplete) bool {
db := database.GetDB()
if db == nil {
return false
}
if err := bootstrapFromLegacyDBMate(db); err != nil {
sqlDB, err := db.DB()
if err != nil {
logger.Error("MigrationError", err)
return false
}
m := gormigrate.New(db, gormigrate.DefaultOptions, all())
if err := m.Migrate(); err != nil {
if err := migrate(sqlDB); err != nil {
logger.Error("MigrationError", err)
return false
}
@@ -63,45 +54,23 @@ func Migrate(followup afterMigrationComplete) bool {
return true
}
// bootstrapFromLegacyDBMate detects a database that was migrated by the
// old dbmate setup and pre-marks the gormigrate migrations that replace
// dbmate's initial_schema/initial_data as already applied, so gormigrate
// doesn't try to recreate tables or re-insert seed data that already
// exist. It's a no-op for a brand new database and for one that has
// already been bootstrapped.
func bootstrapFromLegacyDBMate(db *gorm.DB) error {
if !db.Migrator().HasTable(legacyMigrationsTable) {
// Fresh install, nothing to bootstrap.
return nil
// migrate applies every pending file in embed/migrations/ for the
// configured database engine.
func migrate(sqlDB *sql.DB) error {
engine := config.Configuration.DB.GetDriver()
dialect := polyschema.DialectByName(engine)
if dialect == nil {
return fmt.Errorf("migrations: unsupported database driver %q", engine)
}
var legacyCount int64
if err := db.Table(legacyMigrationsTable).Count(&legacyCount).Error; err != nil {
return err
}
if legacyCount == 0 {
return nil
}
if db.Migrator().HasTable(gormigrate.DefaultOptions.TableName) {
// Already bootstrapped (or running fresh under gormigrate already).
return nil
}
logger.Info("Existing dbmate-managed database detected, marking bootstrap migrations as applied")
type migrationRecord struct {
ID string `gorm:"primaryKey;column:id;size:255"`
}
if err := db.Table(gormigrate.DefaultOptions.TableName).AutoMigrate(&migrationRecord{}); err != nil {
files, err := fs.Sub(embed.MigrationFiles, "migrations")
if err != nil {
return err
}
for _, id := range legacyMigrationIDs {
if err := db.Table(gormigrate.DefaultOptions.TableName).Create(&migrationRecord{ID: id}).Error; err != nil {
return err
}
}
return nil
m := polyschema.New(sqlDB, dialect,
polyschema.WithTable(historyTable),
polyschema.WithLogger(newSlogLogger()),
)
return m.Up(context.Background(), files)
}
+27 -32
View File
@@ -1,9 +1,12 @@
package migrations
import (
"io/fs"
"path/filepath"
"testing"
"npm/embed"
"npm/internal/config"
"npm/internal/database"
"npm/internal/entity"
"npm/internal/entity/certificateauthority"
@@ -12,16 +15,18 @@ import (
"npm/internal/entity/user"
"github.com/glebarez/sqlite"
"github.com/go-gormigrate/gormigrate/v2"
polyschema "github.com/jc21/polyschema/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/gorm"
gormlogger "gorm.io/gorm/logger"
)
func openTestDB(t *testing.T) *gorm.DB {
t.Helper()
config.Configuration.DB.Driver = config.DatabaseSqlite
dsn := filepath.Join(t.TempDir(), "test.db")
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{Logger: gormlogger.Default.LogMode(gormlogger.Silent)})
require.NoError(t, err)
return db
}
@@ -55,9 +60,9 @@ func TestMigrateFreshDatabase(t *testing.T) {
require.NoError(t, db.Model(&nginxtemplate.Model{}).Count(&templateCount).Error)
assert.EqualValues(t, 5, templateCount)
var migrationCount int64
require.NoError(t, db.Table(gormigrate.DefaultOptions.TableName).Count(&migrationCount).Error)
assert.EqualValues(t, len(all()), migrationCount)
var historyCount int64
require.NoError(t, db.Table(historyTable).Count(&historyCount).Error)
assert.EqualValues(t, 2, historyCount, "one history row per migration file")
}
func TestMigrateIsIdempotent(t *testing.T) {
@@ -76,36 +81,26 @@ func TestMigrateIsIdempotent(t *testing.T) {
assert.EqualValues(t, 1, userCount)
}
// TestBootstrapFromLegacyDBMate simulates a database that was previously
// migrated by dbmate: it has dbmate's schema_migrations table with the two
// legacy versions recorded, and already contains seeded data. Migrate must
// detect this and mark the equivalent gormigrate migrations as already
// applied instead of re-running them and duplicating the seed data.
func TestBootstrapFromLegacyDBMate(t *testing.T) {
db := openTestDB(t)
database.SetDB(db)
require.NoError(t, db.Exec(`CREATE TABLE schema_migrations (version TEXT PRIMARY KEY)`).Error)
for _, v := range legacyMigrationIDs {
require.NoError(t, db.Exec(`INSERT INTO schema_migrations (version) VALUES (?)`, v).Error)
}
require.NoError(t, db.Exec(`CREATE TABLE capability (name TEXT PRIMARY KEY)`).Error)
require.NoError(t, db.Exec(`INSERT INTO capability (name) VALUES (?)`, "full-admin").Error)
require.True(t, Migrate(func() {}))
var capabilityCount int64
require.NoError(t, db.Table("capability").Count(&capabilityCount).Error)
assert.EqualValues(t, 1, capabilityCount, "legacy data must not be re-seeded")
var migrationIDs []string
require.NoError(t, db.Table(gormigrate.DefaultOptions.TableName).Pluck("id", &migrationIDs).Error)
assert.ElementsMatch(t, legacyMigrationIDs, migrationIDs)
}
func TestMigrateNoDB(t *testing.T) {
database.SetDB(nil)
assert.False(t, Migrate(func() {
t.Error("followup should not be called")
}))
}
// TestMigrationsRenderForEveryEngine catches a migration file that won't
// run on one of the supported engines, without needing those databases.
func TestMigrationsRenderForEveryEngine(t *testing.T) {
files, err := fs.Sub(embed.MigrationFiles, "migrations")
require.NoError(t, err)
issues, err := polyschema.CheckFS(files, polyschema.Postgres, polyschema.MySQL, polyschema.SQLite)
require.NoError(t, err)
for _, is := range issues {
if is.Severity == polyschema.Error {
t.Error(is)
} else {
t.Log(is)
}
}
}
-68
View File
@@ -1,68 +0,0 @@
package migrations
import (
"npm/internal/entity"
"npm/internal/entity/accesslist"
"npm/internal/entity/auth"
"npm/internal/entity/certificate"
"npm/internal/entity/certificateauthority"
"npm/internal/entity/dnsprovider"
"npm/internal/entity/host"
"npm/internal/entity/nginxtemplate"
"npm/internal/entity/setting"
"npm/internal/entity/stream"
"npm/internal/entity/upstream"
"npm/internal/entity/upstreamserver"
"npm/internal/entity/user"
"npm/internal/jwt"
"npm/internal/model"
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
// auditLogSchema mirrors the audit_log table. There is no application
// entity for it yet, so its schema is only declared here for AutoMigrate.
type auditLogSchema struct {
model.Base
UserID uint `gorm:"column:user_id"`
ObjectType string `gorm:"column:object_type"`
ObjectID uint `gorm:"column:object_id"`
Action string `gorm:"column:action"`
Meta string `gorm:"column:meta"`
}
// TableName overrides the table name used by gorm
func (auditLogSchema) TableName() string {
return "audit_log"
}
// initialSchema creates every table used by the application. It replaces
// the hand-written, per-engine SQL that dbmate used to run: gorm's dialect
// drivers already know how to translate these structs for Postgres, MySQL
// and SQLite, so there's no need to maintain three copies of the same DDL.
func initialSchema() *gormigrate.Migration {
return &gormigrate.Migration{
ID: "20201013035318",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
&jwt.KeysModel{},
&user.Model{},
&entity.Capability{},
&user.HasCapabilityModel{},
&auth.Model{},
&setting.Model{},
&auditLogSchema{},
&certificateauthority.Model{},
&dnsprovider.Model{},
&certificate.Model{},
&stream.Model{},
&nginxtemplate.Model{},
&upstream.Model{},
&upstreamserver.Model{},
&accesslist.Model{},
&host.Model{},
)
},
}
}
+2 -7
View File
@@ -4,7 +4,6 @@ import (
"testing"
"npm/internal/entity/host"
"npm/internal/model"
"npm/internal/test"
"github.com/stretchr/testify/assert"
@@ -25,9 +24,7 @@ func TestGetHostFilename(t *testing.T) {
{
"test1",
host.Model{
Base: model.Base{
ID: 10,
},
ID: 10,
},
"",
"/data/nginx/hosts/host_10.conf",
@@ -35,9 +32,7 @@ func TestGetHostFilename(t *testing.T) {
{
"test2",
host.Model{
Base: model.Base{
ID: 10,
},
ID: 10,
},
".deleted",
"/data/nginx/hosts/host_10.conf.deleted",
+7 -16
View File
@@ -50,10 +50,8 @@ func GetFilterMap(m any, globalTablePrefix string) map[string]model.FilterMapVal
}
// Iterate over all available fields and read the tag value
for i := 0; i < t.NumField(); i++ {
// Get the field, returns https://golang.org/pkg/reflect/#StructField
field := t.Field(i)
// Each field is a https://golang.org/pkg/reflect/#StructField
for field := range t.Fields() {
// Get the field tag value
filterTag := field.Tag.Get("filter")
dbTag := field.Tag.Get("gorm")
@@ -61,10 +59,9 @@ func GetFilterMap(m any, globalTablePrefix string) map[string]model.FilterMapVal
// Filter -> Schema mapping
if filterTag != "" && filterTag != "-" {
f := model.FilterMapValue{
Model: name,
Model: name,
Schema: getFilterTagSchema(filterTag),
}
f.Schema = getFilterTagSchema(filterTag)
parts := strings.Split(filterTag, ",")
// Filter -> DB Field mapping
@@ -104,15 +101,9 @@ func getFilterTagSchema(filterTag string) string {
}
switch items[1] {
case "number":
fallthrough
case "int":
fallthrough
case "integer":
case "number", "int", "integer":
return intFieldSchema(items[0])
case "bool":
fallthrough
case "boolean":
case "bool", "boolean":
return boolFieldSchema(items[0])
case "date":
return dateFieldSchema(items[0])
@@ -131,7 +122,7 @@ func getFilterTagSchema(filterTag string) string {
// object given and by reading the struct "filter" tags.
func GetFilterSchema(m any) string {
filterMap := GetFilterMap(m, "")
schemas := make([]string, 0)
schemas := make([]string, 0, len(filterMap))
for _, f := range filterMap {
schemas = append(schemas, f.Schema)
+10 -7
View File
@@ -25,7 +25,10 @@ func TestDBDate_Value(t *testing.T) {
}
// Convert the value to int64
unixTime := value.(int64)
unixTime, ok := value.(int64)
if !ok {
t.Fatalf("Expected int64 value, got %T", value)
}
// Convert the unix time back to time.Time
actualTime := time.Unix(unixTime, 0)
@@ -55,11 +58,11 @@ func TestDBDate_Scan(t *testing.T) {
}
// Convert the DBDate's time to unix timestamp for comparison
actualUnixTime := dbDate.Time.Unix()
actualUnixSec := dbDate.Time.Unix()
// Compare the actual unix time with the expected unix time
if actualUnixTime != unixTime {
t.Errorf("Expected unix time '%v', got '%v'", unixTime, actualUnixTime)
if actualUnixSec != unixTime {
t.Errorf("Expected unix time '%v', got '%v'", unixTime, actualUnixSec)
}
}
@@ -82,12 +85,12 @@ func TestDBDate_UnmarshalJSON(t *testing.T) {
}
// Convert the DBDate's time to unix timestamp for comparison
actualUnixTime := dbDate.Time.Unix()
actualUnixSec := dbDate.Time.Unix()
// Compare the actual unix time with the expected unix time
expectedUnixTime := int64(1640995200)
if actualUnixTime != expectedUnixTime {
t.Errorf("Expected unix time '%v', got '%v'", expectedUnixTime, actualUnixTime)
if actualUnixSec != expectedUnixTime {
t.Errorf("Expected unix time '%v', got '%v'", expectedUnixTime, actualUnixSec)
}
}
+2 -2
View File
@@ -25,9 +25,9 @@ func (j *JSONB) Scan(src any) error {
var srcString string
switch v := src.(type) {
case string:
srcString = src.(string)
srcString = v
case []uint8:
srcString = string(src.([]uint8))
srcString = string(v)
default:
return eris.Errorf("Incompatible type for JSONB: %v", v)
}
@@ -22,10 +22,10 @@ func TestNullableDBDateValue(t *testing.T) {
t.Errorf("Unexpected error: %v", err)
}
expectedValue := tme.Unix()
expectedSec := tme.Unix()
if value != expectedValue {
t.Errorf("Incorrect value. Expected: %d, Got: %v", expectedValue, value)
if value != expectedSec {
t.Errorf("Incorrect value. Expected: %d, Got: %v", expectedSec, value)
}
}
@@ -101,10 +101,10 @@ func TestNullableDBDateAsInt64(t *testing.T) {
}
unixtime := d.AsInt64()
expectedUnixtime := tme.Unix()
expectedSec := tme.Unix()
if unixtime != expectedUnixtime {
t.Errorf("Incorrect unixtime. Expected: %d, Got: %d", expectedUnixtime, unixtime)
if unixtime != expectedSec {
t.Errorf("Incorrect unixtime. Expected: %d, Got: %d", expectedSec, unixtime)
}
}
+4 -13
View File
@@ -1,33 +1,24 @@
package util
import (
"slices"
"strconv"
"strings"
)
// SliceContainsItem returns whether the slice given contains the item given
func SliceContainsItem(slice []string, item string) bool {
for _, a := range slice {
if a == item {
return true
}
}
return false
return slices.Contains(slice, item)
}
// SliceContainsInt returns whether the slice given contains the item given
func SliceContainsInt(slice []int, item int) bool {
for _, a := range slice {
if a == item {
return true
}
}
return false
return slices.Contains(slice, item)
}
// ConvertIntSliceToString returns a comma separated string of all items in the slice
func ConvertIntSliceToString(slice []int) string {
strs := []string{}
strs := make([]string, 0, len(slice))
for _, item := range slice {
strs = append(strs, strconv.Itoa(item))
}
+6 -3
View File
@@ -21,7 +21,8 @@ type MockCertificate struct {
func (m *MockCertificate) GetByID(id uint) (certificate.Model, error) {
args := m.Called(id)
return args.Get(0).(certificate.Model), args.Error(1)
model, _ := args.Get(0).(certificate.Model)
return model, args.Error(1)
}
type MockUpstream struct {
@@ -30,7 +31,8 @@ type MockUpstream struct {
func (m *MockUpstream) GetByID(id uint) (upstream.Model, error) {
args := m.Called(id)
return args.Get(0).(upstream.Model), args.Error(1)
model, _ := args.Get(0).(upstream.Model)
return model, args.Error(1)
}
type MockNginxTemplate struct {
@@ -39,7 +41,8 @@ type MockNginxTemplate struct {
func (m *MockNginxTemplate) GetByID(id uint) (nginxtemplate.Model, error) {
args := m.Called(id)
return args.Get(0).(nginxtemplate.Model), args.Error(1)
model, _ := args.Get(0).(nginxtemplate.Model)
return model, args.Error(1)
}
func TestValidateHost(t *testing.T) {
+24 -14
View File
@@ -1,22 +1,32 @@
#!/bin/bash
set -eufo pipefail
BLUE='\E[1;34m'
YELLOW='\E[1;33m'
RESET='\E[0m'
RESULT=0
export RED='\E[1;31m'
export YELLOW='\E[1;33m'
export RESET='\033[0m'
# go files: incomplete comment check
INCOMPLETE_COMMENTS=$(find . -iname "*.go*" | grep -v " " | xargs grep --colour -H -n -E "^\s*\/\/\s*[A-Z]\w+ \.{3}" 2>/dev/null)
if [[ -n "$INCOMPLETE_COMMENTS" ]]; then
echo -e "${BLUE}❯ ${YELLOW}WARN: Please fix incomplete exported comments:${RESET}"
echo -e "${RED}${INCOMPLETE_COMMENTS}${RESET}"
echo
# RESULT=1
PROJECT_DIR="$(cd -- "$(dirname -- "$0")/.." && pwd)"
cd "$PROJECT_DIR"
trap cleanup EXIT
cleanup() {
if [ "$?" -ne 0 ]; then
echo -e "${RED}LINTING FAILED${RESET}"
fi
}
if ! command -v golangci-lint &>/dev/null; then
echo -e "${YELLOW}Installing golangci-lint ...${RESET}"
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
fi
echo -e "${YELLOW}golangci-lint ...${RESET}"
if ! golangci-lint run -E goimports ./...; then
exit 1
golangci-lint --max-same-issues=0 --max-issues-per-linter=0 run ./...
if ! command -v modernize &>/dev/null; then
echo -e "${YELLOW}Installing modernize ...${RESET}"
go install golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest
fi
exit "$RESULT"
echo -e "${YELLOW}modernize ...${RESET}"
modernize -test "$@" ./...
-23
View File
@@ -1,23 +0,0 @@
#!/bin/bash
set -eu
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
if ! command -v go-test-coverage &>/dev/null; then
go install github.com/vladopajic/go-test-coverage/v2@latest
fi
if ! command -v tparse &>/dev/null; then
go install github.com/mfridman/tparse@latest
fi
rm -f "$DIR/coverage.html"
trap cleanup EXIT
cleanup() {
rm -f "$DIR/coverage.out"
}
echo "Running go test suite ..."
go test -json -cover ./... -coverprofile="$DIR/coverage.out" | tparse
go tool cover -html="$DIR/coverage.out" -o "$DIR/coverage.html"
go-test-coverage -c "$DIR/.testcoverage.yml"
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
set -eu
export RED='\E[1;31m'
export YELLOW='\E[1;33m'
export RESET='\033[0m'
cd -- "$(dirname -- "$0")/.." || exit 1
if ! command -v go-test-coverage &>/dev/null; then
echo -e "${YELLOW}Installing go-test-coverage ...${RESET}"
go install github.com/vladopajic/go-test-coverage/v2@latest
fi
if ! command -v tparse &>/dev/null; then
echo -e "${YELLOW}Installing tparse ...${RESET}"
go install github.com/mfridman/tparse@latest
fi
if ! command -v go-junit-report &>/dev/null; then
echo -e "${YELLOW}Installing go-junit-report ...${RESET}"
go install github.com/jstemmer/go-junit-report/v2@latest
fi
trap cleanup EXIT
cleanup() {
if [ "$?" -ne 0 ]; then
echo -e "${RED}UNIT TESTING FAILED - check output and consider minimum coverage requirements${RESET}"
fi
rm -f coverage.out
}
mkdir -p ../test/results/html-reports ../test/results/junit
go test -json -cover -coverprofile="./coverage.out" ./... | tparse || true
go tool cover -html="./coverage.out" -o "../test/results/html-reports/backend-coverage.html"
go test -v -covermode=atomic ./... 2>&1 | go-junit-report >../test/results/junit/backend.xml
# this enforces minimum coverage requirements
go-test-coverage -c .testcoverage.yml