| -rw-r--r-- | .github/workflows/build-and-deploy-prod.yml | 2 | ||||
| -rw-r--r-- | .github/workflows/build-and-deploy-stage.yml | 2 | ||||
| -rw-r--r-- | config/config.go | 85 | ||||
| -rw-r--r-- | config/config.local.yaml | 56 | ||||
| -rw-r--r-- | config/config.prod.yaml | 59 | ||||
| -rw-r--r-- | config/config.stage.yaml | 57 | ||||
| -rw-r--r-- | deploy/inventory.yml | 2 | ||||
| -rw-r--r-- | deploy/playbook.yml | 27 | ||||
| -rw-r--r-- | go.mod | 59 | ||||
| -rw-r--r-- | go.sum | 119 | ||||
| -rw-r--r-- | internal/blog/index.go | 32 | ||||
| -rw-r--r-- | internal/blog/s3.go | 173 | ||||
| -rw-r--r-- | internal/router/router.go | 36 | ||||
| -rw-r--r-- | locale/localization.en.yaml | 120 | ||||
| -rw-r--r-- | locale/localization.ru.yaml | 132 | ||||
| -rw-r--r-- | main.go | 2 | ||||
| -rw-r--r-- | static/input.css | 2 | ||||
| -rw-r--r-- | views/layouts/general-mail.html | 196 | ||||
| -rw-r--r-- | views/layouts/general-page.html | 3 | ||||
| -rw-r--r-- | views/pages/global-map.html | 601 | ||||
| -rw-r--r-- | views/pages/robots.txt | 4 | ||||
| -rw-r--r-- | views/pages/unsubscribe-page.html | 87 | ||||
| -rw-r--r-- | views/partials/general-page-footer.html | 26 | ||||
| -rw-r--r-- | views/partials/general-page-header.html | 10 |
24 files changed, 702 insertions, 1190 deletions
diff --git a/.github/workflows/build-and-deploy-prod.yml b/.github/workflows/build-and-deploy-prod.yml index 381d656..d89cc76 100644 --- a/.github/workflows/build-and-deploy-prod.yml +++ b/.github/workflows/build-and-deploy-prod.yml @@ -80,4 +80,4 @@ jobs: ANSIBLE_HOST_KEY_CHECKING: False working-directory: ./deploy run: | - ansible-playbook -i inventory.yml -l prod playbook.yml --private-key ~/.ssh/id_ed25519 -u svc_github -e saya_today_web_auth_salt="${{ secrets.AUTH_SALT }}" -e saya_today_web_s3_access_key_id="${{ secrets.S3_ACCESS_KEY_ID }}" -e saya_today_web_s3_secret_access_key="${{ secrets.S3_SECRET_ACCESS_KEY }}" -e saya_today_web_environment=prod -e saya_today_web_tag=${{ github.ref_name }} -e saya_today_web_mail_salt="${{ secrets.MAIL_SALT }}" -e saya_today_web_mail_host="${{ secrets.MAIL_HOST }}" -e saya_today_web_mail_address="${{ secrets.MAIL_ADDRESS }}" -e saya_today_web_mail_username="${{ secrets.MAIL_USERNAME }}" -e saya_today_web_mail_password="${{ secrets.MAIL_PASSWORD }}" -e saya_today_google_site_verification="${{ secrets.GOOGLE_SITE_VERIFICATION }}" + ansible-playbook -i inventory.yml -l prod playbook.yml --private-key ~/.ssh/id_ed25519 -u svc_github -e saya_today_web_auth_salt="${{ secrets.AUTH_SALT }}" -e saya_today_web_b2_key_id="${{ secrets.B2_KEY_ID }}" -e saya_today_web_b2_application_key="${{ secrets.B2_APPLICATION_KEY }}" -e saya_today_web_environment=prod -e saya_today_web_tag=${{ github.ref_name }} -e saya_today_web_mail_salt="${{ secrets.MAIL_SALT }}" -e saya_today_web_mail_host="${{ secrets.MAIL_HOST }}" -e saya_today_web_mail_address="${{ secrets.MAIL_ADDRESS }}" -e saya_today_web_mail_username="${{ secrets.MAIL_USERNAME }}" -e saya_today_web_mail_password="${{ secrets.MAIL_PASSWORD }}" -e saya_today_google_site_verification="${{ secrets.GOOGLE_SITE_VERIFICATION }}" diff --git a/.github/workflows/build-and-deploy-stage.yml b/.github/workflows/build-and-deploy-stage.yml index ffee091..e699856 100644 --- a/.github/workflows/build-and-deploy-stage.yml +++ b/.github/workflows/build-and-deploy-stage.yml @@ -86,4 +86,4 @@ jobs: ANSIBLE_HOST_KEY_CHECKING: False working-directory: ./deploy run: | - ansible-playbook -i inventory.yml -l stage playbook.yml --private-key ~/.ssh/id_ed25519 -u svc_github -e saya_today_web_auth_salt="${{ secrets.AUTH_SALT }}" -e saya_today_web_s3_access_key_id="${{ secrets.S3_ACCESS_KEY_ID }}" -e saya_today_web_s3_secret_access_key="${{ secrets.S3_SECRET_ACCESS_KEY }}" -e saya_today_web_environment=stage -e saya_today_web_tag=commit-${{ steps.ghss_vars.outputs.sha_short }} -e saya_today_web_mail_salt="${{ secrets.MAIL_SALT }}" -e saya_today_web_mail_host="${{ secrets.MAIL_HOST }}" -e saya_today_web_mail_address="${{ secrets.MAIL_ADDRESS }}" -e saya_today_web_mail_username="${{ secrets.MAIL_USERNAME }}" -e saya_today_web_mail_password="${{ secrets.MAIL_PASSWORD }}" + ansible-playbook -i inventory.yml -l stage playbook.yml --private-key ~/.ssh/id_ed25519 -u svc_github -e saya_today_web_auth_salt="${{ secrets.AUTH_SALT }}" -e saya_today_web_b2_key_id="${{ secrets.B2_KEY_ID }}" -e saya_today_web_b2_application_key="${{ secrets.B2_APPLICATION_KEY }}" -e saya_today_web_environment=stage -e saya_today_web_tag=commit-${{ steps.ghss_vars.outputs.sha_short }} -e saya_today_web_mail_salt="${{ secrets.MAIL_SALT }}" -e saya_today_web_mail_host="${{ secrets.MAIL_HOST }}" -e saya_today_web_mail_address="${{ secrets.MAIL_ADDRESS }}" -e saya_today_web_mail_username="${{ secrets.MAIL_USERNAME }}" -e saya_today_web_mail_password="${{ secrets.MAIL_PASSWORD }}" diff --git a/config/config.go b/config/config.go index 784ac38..536a333 100644 --- a/config/config.go +++ b/config/config.go @@ -12,7 +12,6 @@ import ( type Config struct { LogLevel slog.Level `json:"LogLevel" yaml:"logLevel" validate:"required"` - Endpoint EndpointConfig `json:"Endpoint" yaml:"endpoint" validate:"required"` BlogPages BlogPagesConfig `json:"BlogPages" yaml:"blogPages" validate:"required"` FactGiver FactGiverConfig `json:"FactGiver" yaml:"factGiver" validate:"required"` LocalePath string `json:"LocalePath" yaml:"localePath" validate:"required,filepath"` @@ -26,90 +25,12 @@ type Config struct { AllowOrigins []string `json:"AllowOrigins" yaml:"allowOrigins"` } -type EndpointConfig struct { - Type string `json:"Type" yaml:"type" validate:"required,oneof=http unix"` - Config any `json:"Config" yaml:"config" validate:"required"` -} - -func (ec *EndpointConfig) UnmarshalJSON(data []byte) error { - var tmp struct { - Type string `json:"Type"` - Config json.RawMessage `json:"Config"` - } - - if err := json.Unmarshal(data, &tmp); err != nil { - return err - } - - ec.Type = tmp.Type - - switch tmp.Type { - case "http": - var httpConfig HttpConfig - if err := json.Unmarshal(tmp.Config, &httpConfig); err != nil { - return fmt.Errorf("unmarshal HttpConfig: %w", err) - } - ec.Config = &httpConfig - case "unix": - var unixConfig UnixConfig - if err := json.Unmarshal(tmp.Config, &unixConfig); err != nil { - return fmt.Errorf("unmarshal UnixConfig: %w", err) - } - ec.Config = &unixConfig - default: - return fmt.Errorf("unsupported storage type: %s", tmp.Type) - } - - return nil -} - -func (ec *EndpointConfig) UnmarshalYAML(value *yaml.Node) error { - var tmp struct { - Type string `yaml:"type"` - Config yaml.Node `yaml:"config"` - } - - if err := value.Decode(&tmp); err != nil { - return err - } - - ec.Type = tmp.Type - - switch tmp.Type { - case "http": - var httpConfig HttpConfig - if err := tmp.Config.Decode(&httpConfig); err != nil { - return fmt.Errorf("unmarshal HttpConfig: %w", err) - } - ec.Config = &httpConfig - case "unix": - var unixConfig UnixConfig - if err := tmp.Config.Decode(&unixConfig); err != nil { - return fmt.Errorf("unmarshal UnixConfig: %w", err) - } - ec.Config = &unixConfig - default: - return fmt.Errorf("unsupported storage type: %s", tmp.Type) - } - - return nil -} - -type HttpConfig struct { - ListenOn string `json:"ListenOn" yaml:"listenOn" validate:"required"` -} - -type UnixConfig struct { - Path string `json:"Path" yaml:"path" validate:"required"` - Chmod string `json:"Chmod" yaml:"chmod" validate:"oneof=0600 0660 0666"` -} - type BlogPagesConfig struct { Storage StorageConfig `json:"Storage" yaml:"storage" validate:"required"` } type StorageConfig struct { - Type string `json:"Type" yaml:"type" validate:"required,oneof=b2 s3"` + Type string `json:"type" yaml:"type,oneof=b2 s3"` Config any `json:"Config" yaml:"config" validate:"required"` } @@ -281,10 +202,6 @@ func InitConfig(path string) (*Config, error) { return nil, err } - if config.Endpoint.Type == "unix" && config.Endpoint.Config.(*UnixConfig).Chmod == "" { - config.Endpoint.Config.(*UnixConfig).Chmod = "0660" - } - validate := validator.New(validator.WithRequiredStructEnabled()) if err := validate.Struct(config); err != nil { return nil, err diff --git a/config/config.local.yaml b/config/config.local.yaml index d3e2e33..fb79e97 100644 --- a/config/config.local.yaml +++ b/config/config.local.yaml @@ -1,31 +1,23 @@ logLevel: debug -endpoint: - type: http - config: - listenOn: ":3000" blogPages: storage: - type: s3 + type: b2 config: - bucketName: sayauz-pages - region: kz1 - prefix: "stage-" - endpoint: "https://storage.yandexcloud.kz" - usePathStyle: true - accessKeyID: "${S3_ACCESS_KEY_ID}" - secretAccessKey: "${S3_SECRET_ACCESS_KEY}" + bucketName: sayana-pages + region: eu-central-003 + prefix: 'stage-' + keyID: '${B2_KEY_ID}' + applicationKey: '${B2_APPLICATION_KEY}' factGiver: storage: - type: s3 + type: b2 config: - bucketName: sayauz-pages - region: kz1 - prefix: "" - endpoint: "https://storage.yandexcloud.kz" - usePathStyle: true - accessKeyID: "${S3_ACCESS_KEY_ID}" - secretAccessKey: "${S3_SECRET_ACCESS_KEY}" - factsFileName: "facts-*.txt" + bucketName: sayana-pages + region: eu-central-003 + prefix: '' + keyID: '${B2_KEY_ID}' + applicationKey: '${B2_APPLICATION_KEY}' + factsFileName: 'facts-*.txt' localePath: ./locale/ availableLanguages: - name: ru @@ -41,19 +33,19 @@ auth: type: sqlite3 config: # dsn: 'file:/tmp/auth.db?cache=shared&mode=rwc&_journal_mode=WAL' - dsn: "file:/tmp/auth.db?cache=private&mode=rwc&_locking_mode=EXCLUSIVE&_mutex=no&_auto_vacuum=2&_journal_mode=WAL" - salt: "123" + dsn: 'file:/tmp/auth.db?cache=private&mode=rwc&_locking_mode=EXCLUSIVE&_mutex=no&_auto_vacuum=2&_journal_mode=WAL' + salt: '123' mail: - clientHost: "127.0.0.1:3000" - mailHost: "${MAIL_HOST}" - publicName: "LOCAL.SAYA.UZ" - mailAddress: "${MAIL_ADDRESS}" - username: "${MAIL_USERNAME}" - password: "${MAIL_PASSWORD}" - salt: "${MAIL_SALT}" + clientHost: '127.0.0.1:3000' + mailHost: '${MAIL_HOST}' + publicName: 'LOCAL.SAYA.TODAY' + mailAddress: '${MAIL_ADDRESS}' + username: '${MAIL_USERNAME}' + password: '${MAIL_PASSWORD}' + salt: '${MAIL_SALT}' trigger: onNewPost: "* * * * *" -canonicalEndpoint: "http://127.0.0.1:3000" +canonicalEndpoint: 'http://127.0.0.1:3000' photoStorage: full: baseUrl: https://storage.yandexcloud.kz/sayauz-photos/%s @@ -71,7 +63,7 @@ photoStorage: baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp320/%s.webp homePageGifs: baseUrl: https://storage.yandexcloud.kz/sayauz-static/home-page-gifs/otter-%s.gif - indexes: ["1", "2", "3"] + indexes: ['1', '2', '3'] staticStorage: baseUrl: https://storage.yandexcloud.kz/sayauz-static allowOrigins: diff --git a/config/config.prod.yaml b/config/config.prod.yaml index 779550a..868e242 100644 --- a/config/config.prod.yaml +++ b/config/config.prod.yaml @@ -1,32 +1,23 @@ logLevel: info -endpoint: - type: unix - config: - path: "/etc/sayauz/prod.socket" - chmod: '0666' blogPages: storage: - type: s3 + type: b2 config: - bucketName: sayauz-pages - region: kz1 - prefix: "prod-" - endpoint: "https://storage.yandexcloud.kz" - usePathStyle: true - accessKeyID: "${S3_ACCESS_KEY_ID}" - secretAccessKey: "${S3_SECRET_ACCESS_KEY}" + bucketName: sayana-pages + region: eu-central-003 + prefix: 'prod-' + keyID: '${B2_KEY_ID}' + applicationKey: '${B2_APPLICATION_KEY}' factGiver: storage: - type: s3 + type: b2 config: - bucketName: sayauz-pages - region: kz1 - prefix: "" - endpoint: "https://storage.yandexcloud.kz" - usePathStyle: true - accessKeyID: "${S3_ACCESS_KEY_ID}" - secretAccessKey: "${S3_SECRET_ACCESS_KEY}" - factsFileName: "facts-*.txt" + bucketName: sayana-pages + region: eu-central-003 + prefix: '' + keyID: '${B2_KEY_ID}' + applicationKey: '${B2_APPLICATION_KEY}' + factsFileName: 'facts-*.txt' localePath: ./locale/ availableLanguages: - name: ru @@ -41,21 +32,21 @@ auth: db: type: sqlite3 config: - dsn: "file:/data/auth.db?cache=private&mode=rwc&_locking_mode=EXCLUSIVE&_mutex=no&_auto_vacuum=2&_journal_mode=WAL" - salt: "${AUTH_SALT}" + dsn: 'file:/data/auth.db?cache=private&mode=rwc&_locking_mode=EXCLUSIVE&_mutex=no&_auto_vacuum=2&_journal_mode=WAL' + salt: '${AUTH_SALT}' mail: - clientHost: "${FQDN}" - mailHost: "${MAIL_HOST}" - publicName: "SAYA.UZ" - mailAddress: "${MAIL_ADDRESS}" - username: "${MAIL_USERNAME}" - password: "${MAIL_PASSWORD}" - salt: "${MAIL_SALT}" + clientHost: '${FQDN}' + mailHost: '${MAIL_HOST}' + publicName: 'SAYA.TODAY' + mailAddress: '${MAIL_ADDRESS}' + username: '${MAIL_USERNAME}' + password: '${MAIL_PASSWORD}' + salt: '${MAIL_SALT}' trigger: onNewPost: "0/5 * * * *" -canonicalEndpoint: "https://${FQDN}" +canonicalEndpoint: 'https://${FQDN}' meta: - googleSiteVerification: "${GOOGLE_SITE_VERIFICATION}" + googleSiteVerification: '${GOOGLE_SITE_VERIFICATION}' photoStorage: full: baseUrl: https://storage.yandexcloud.kz/sayauz-photos/%s @@ -73,7 +64,7 @@ photoStorage: baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp320/%s.webp homePageGifs: baseUrl: https://storage.yandexcloud.kz/sayauz-static/home-page-gifs/otter-%s.gif - indexes: ["1", "2", "3"] + indexes: ['1', '2', '3'] staticStorage: baseUrl: https://storage.yandexcloud.kz/sayauz-static allowOrigins: diff --git a/config/config.stage.yaml b/config/config.stage.yaml index c2e337b..ab86709 100644 --- a/config/config.stage.yaml +++ b/config/config.stage.yaml @@ -1,32 +1,23 @@ logLevel: debug -endpoint: - type: unix - config: - path: "/etc/sayauz/stage.socket" - chmod: '0666' blogPages: storage: - type: s3 + type: b2 config: - bucketName: sayauz-pages - region: kz1 - prefix: "stage-" - endpoint: "https://storage.yandexcloud.kz" - usePathStyle: true - accessKeyID: "${S3_ACCESS_KEY_ID}" - secretAccessKey: "${S3_SECRET_ACCESS_KEY}" + bucketName: sayana-pages + region: eu-central-003 + prefix: 'stage-' + keyID: '${B2_KEY_ID}' + applicationKey: '${B2_APPLICATION_KEY}' factGiver: storage: - type: s3 + type: b2 config: - bucketName: sayauz-pages - region: kz1 - prefix: "" - endpoint: "https://storage.yandexcloud.kz" - usePathStyle: true - accessKeyID: "${S3_ACCESS_KEY_ID}" - secretAccessKey: "${S3_SECRET_ACCESS_KEY}" - factsFileName: "facts-*.txt" + bucketName: sayana-pages + region: eu-central-003 + prefix: '' + keyID: '${B2_KEY_ID}' + applicationKey: '${B2_APPLICATION_KEY}' + factsFileName: 'facts-*.txt' localePath: ./locale/ availableLanguages: - name: ru @@ -41,19 +32,19 @@ auth: db: type: sqlite3 config: - dsn: "file:/data/auth.db?cache=private&mode=rwc&_locking_mode=EXCLUSIVE&_mutex=no&_auto_vacuum=2&_journal_mode=WAL" - salt: "${AUTH_SALT}" + dsn: 'file:/data/auth.db?cache=private&mode=rwc&_locking_mode=EXCLUSIVE&_mutex=no&_auto_vacuum=2&_journal_mode=WAL' + salt: '${AUTH_SALT}' mail: - clientHost: "${FQDN}" - mailHost: "${MAIL_HOST}" - publicName: "STAGE.SAYA.UZ" - mailAddress: "${MAIL_ADDRESS}" - username: "${MAIL_USERNAME}" - password: "${MAIL_PASSWORD}" - salt: "${MAIL_SALT}" + clientHost: '${FQDN}' + mailHost: '${MAIL_HOST}' + publicName: 'DEMO.SAYA.TODAY' + mailAddress: '${MAIL_ADDRESS}' + username: '${MAIL_USERNAME}' + password: '${MAIL_PASSWORD}' + salt: '${MAIL_SALT}' trigger: onNewPost: "0/3 * * * *" -canonicalEndpoint: "https://${FQDN}" +canonicalEndpoint: 'https://${FQDN}' photoStorage: full: baseUrl: https://storage.yandexcloud.kz/sayauz-photos/%s @@ -71,7 +62,7 @@ photoStorage: baseUrl: https://storage.yandexcloud.kz/sayauz-thumbnails-webp320/%s.webp homePageGifs: baseUrl: https://storage.yandexcloud.kz/sayauz-static/home-page-gifs/otter-%s.gif - indexes: ["1", "2", "3"] + indexes: ['1', '2', '3'] staticStorage: baseUrl: https://storage.yandexcloud.kz/sayauz-static allowOrigins: diff --git a/deploy/inventory.yml b/deploy/inventory.yml index 0a63140..d3acab8 100644 --- a/deploy/inventory.yml +++ b/deploy/inventory.yml @@ -8,7 +8,6 @@ all: saya_today_web_hostname: sayauz-prod saya_today_web_ipv4_address: 172.16.0.17 saya_today_web_listen_address: saya.uz - saya_today_web_unix_sockets_path: '/etc/sayauz' stage: hosts: @@ -18,4 +17,3 @@ all: saya_today_web_hostname: sayauz-stage saya_today_web_ipv4_address: 172.16.0.18 saya_today_web_listen_address: stage.saya.uz - saya_today_web_unix_sockets_path: '/etc/sayauz' diff --git a/deploy/playbook.yml b/deploy/playbook.yml index b73146a..8fb9088 100644 --- a/deploy/playbook.yml +++ b/deploy/playbook.yml @@ -2,27 +2,9 @@ - name: Deploy saya-today-web Docker Container hosts: all remote_user: svc_github - - vars: - docker_volumes: - - "{{ saya_today_web_name }}-volume:/data:rw" tasks: - - name: Initialize Unix socket volume - when: >- - saya_today_web_unix_sockets_path is defined and - saya_today_web_unix_sockets_path is string and - saya_today_web_unix_sockets_path | length > 0 - block: - - name: Ensure Unix socket volume exists - community.docker.docker_volume: - name: "sayana_sockets" - - name: Include Unix socket volume in docker volumes list - ansible.builtin.set_fact: - docker_volumes: >- - {{ docker_volumes + ["sayana_sockets:" ~ saya_today_web_unix_sockets_path ~ ":rw"] }} - - - name: Create a data volume + - name: Create a volume community.docker.docker_volume: name: "{{ saya_today_web_name }}-volume" @@ -32,8 +14,8 @@ hostname: "{{ saya_today_web_hostname }}" image: "ghcr.io/sayaandy/saya-today-web:{{ saya_today_web_tag }}" env: - S3_ACCESS_KEY_ID: "{{ saya_today_web_s3_access_key_id }}" - S3_SECRET_ACCESS_KEY: "{{ saya_today_web_s3_secret_access_key }}" + B2_KEY_ID: "{{ saya_today_web_b2_key_id }}" + B2_APPLICATION_KEY: "{{ saya_today_web_b2_application_key }}" ENVIRONMENT: "{{ saya_today_web_environment }}" AUTH_SALT: "{{ saya_today_web_auth_salt }}" MAIL_HOST: "{{ saya_today_web_mail_host }}" @@ -47,7 +29,8 @@ networks: - name: caddy ipv4_address: "{{ saya_today_web_ipv4_address }}" - volumes: "{{ docker_volumes }}" + volumes: + - "{{ saya_today_web_name }}-volume:/data:rw" restart_policy: unless-stopped no_log: true ... @@ -5,43 +5,44 @@ go 1.26 require ( github.com/Backblaze/blazer v0.7.2 github.com/dgraph-io/ristretto/v2 v2.4.0 - github.com/go-co-op/gocron/v2 v2.21.1 + github.com/go-co-op/gocron/v2 v2.20.0 github.com/go-playground/validator/v10 v10.30.2 - github.com/gofiber/fiber/v2 v2.52.13 + github.com/gofiber/fiber/v2 v2.52.12 github.com/golang-migrate/migrate/v4 v4.19.1 - github.com/mattn/go-sqlite3 v1.14.44 + github.com/mattn/go-sqlite3 v1.14.41 github.com/valyala/fasthttp v1.70.0 github.com/wneessen/go-mail v0.7.2 github.com/yuin/goldmark v1.8.2 - golang.org/x/crypto v0.50.0 + golang.org/x/crypto v0.49.0 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 // indirect - github.com/philhofer/fwd v1.2.0 // indirect - github.com/tinylib/msgp v1.6.4 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.15 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 // indirect + github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect + github.com/tinylib/msgp v1.2.5 // indirect ) require ( github.com/andybalholm/brotli v1.2.1 // indirect - github.com/aws/aws-sdk-go-v2 v1.41.7 - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 // indirect - github.com/aws/aws-sdk-go-v2/config v1.32.17 - github.com/aws/aws-sdk-go-v2/credentials v1.19.16 - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 // indirect - github.com/aws/aws-sdk-go-v2/service/s3 v1.100.1 - github.com/aws/smithy-go v1.25.1 // indirect + github.com/aws/aws-sdk-go-v2 v1.41.5 + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.14 + github.com/aws/aws-sdk-go-v2/credentials v1.19.14 + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.99.0 + github.com/aws/smithy-go v1.24.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect @@ -50,14 +51,14 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect - github.com/klauspost/compress v1.18.6 // indirect + github.com/klauspost/compress v1.18.5 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.22 // indirect - github.com/mattn/go-runewidth v0.0.23 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.22 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/text v0.36.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect ) @@ -2,42 +2,44 @@ github.com/Backblaze/blazer v0.7.2 h1:UWNHMLB+Nf+UmbO2qkVvgriODLEMz4kIyr2Hm+DVXQ github.com/Backblaze/blazer v0.7.2/go.mod h1:T4y3EYa9IQ5J0PKc/C/J8/CEnSd3qa/lgNw938wZg10= github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro= github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= -github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8= -github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 h1:gx1AwW1Iyk9Z9dD9F4akX5gnN3QZwUB20GGKH/I+Rho= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10/go.mod h1:qqY157uZoqm5OXq/amuaBJyC9hgBCBQnsaWnPe905GY= -github.com/aws/aws-sdk-go-v2/config v1.32.17 h1:FpL4/758/diKwqbytU0prpuiu60fgXKUWCpDJtApclU= -github.com/aws/aws-sdk-go-v2/config v1.32.17/go.mod h1:OXqUMzgXytfoF9JaKkhrOYsyh72t9G+MJH8mMRaexOE= -github.com/aws/aws-sdk-go-v2/credentials v1.19.16 h1:r3RJBuU7X9ibt8RHbMjWE6y60QbKBiII6wSrXnapxSU= -github.com/aws/aws-sdk-go-v2/credentials v1.19.16/go.mod h1:6cx7zqDENJDbBIIWX6P8s0h6hqHC8Avbjh9Dseo27ug= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 h1:UuSfcORqNSz/ey3VPRS8TcVH2Ikf0/sC+Hdj400QI6U= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23/go.mod h1:+G/OSGiOFnSOkYloKj/9M35s74LgVAdJBSD5lsFfqKg= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23/go.mod h1:15DfR2nw+CRHIk0tqNyifu3G1YdAOy68RftkhMDDwYk= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 h1:ieLCO1JxUWuxTZ1cRd0GAaeX7O6cIxnwk7tc1LsQhC4= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15/go.mod h1:e3IzZvQ3kAWNykvE0Tr0RDZCMFInMvhku3qNpcIQXhM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 h1:pbrxO/kuIwgEsOPLkaHu0O+m4fNgLU8B3vxQ+72jTPw= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23/go.mod h1:/CMNUqoj46HpS3MNRDEDIwcgEnrtZlKRaHNaHxIFpNA= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 h1:03xatSQO4+AM1lTAbnRg5OK528EUg744nW7F73U8DKw= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23/go.mod h1:M8l3mwgx5ToK7wot2sBBce/ojzgnPzZXUV445gTSyE8= -github.com/aws/aws-sdk-go-v2/service/s3 v1.100.1 h1:mxuT1xE+dI54NW3RkNjP8DUT5HXqbkiAFvfdyDFwE5c= -github.com/aws/aws-sdk-go-v2/service/s3 v1.100.1/go.mod h1:L2dcoOgS2VSgbPLvpak2NyUPsO1TBN7M45Z4H7DlRc4= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 h1:TdJ+HdzOBhU8+iVAOGUTU63VXopcumCOF1paFulHWZc= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.11/go.mod h1:R82ZRExE/nheo0N+T8zHPcLRTcH8MGsnR3BiVGX0TwI= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 h1:7byT8HUWrgoRp6sXjxtZwgOKfhss5fW6SkLBtqzgRoE= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.17/go.mod h1:xNWknVi4Ezm1vg1QsB/5EWpAJURq22uqd38U8qKvOJc= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 h1:+1Kl1zx6bWi4X7cKi3VYh29h8BvsCoHQEQ6ST9X8w7w= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21/go.mod h1:4vIRDq+CJB2xFAXZ+YgGUTiEft7oAQlhIs71xcSeuVg= -github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 h1:F/M5Y9I3nwr2IEpshZgh1GeHpOItExNM9L1euNuh/fk= -github.com/aws/aws-sdk-go-v2/service/sts v1.42.1/go.mod h1:mTNxImtovCOEEuD65mKW7DCsL+2gjEH+RPEAexAzAio= -github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI= -github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY= +github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= +github.com/aws/aws-sdk-go-v2/config v1.32.14 h1:opVIRo/ZbbI8OIqSOKmpFaY7IwfFUOCCXBsUpJOwDdI= +github.com/aws/aws-sdk-go-v2/config v1.32.14/go.mod h1:U4/V0uKxh0Tl5sxmCBZ3AecYny4UNlVmObYjKuuaiOo= +github.com/aws/aws-sdk-go-v2/credentials v1.19.14 h1:n+UcGWAIZHkXzYt87uMFBv/l8THYELoX6gVcUvgl6fI= +github.com/aws/aws-sdk-go-v2/credentials v1.19.14/go.mod h1:cJKuyWB59Mqi0jM3nFYQRmnHVQIcgoxjEMAbLkpr62w= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 h1:NUS3K4BTDArQqNu2ih7yeDLaS3bmHD0YndtA6UP884g= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21/go.mod h1:YWNWJQNjKigKY1RHVJCuupeWDrrHjRqHm0N9rdrWzYI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.99.0 h1:hlSuz394kV0vhv9drL5lhuEFbEOEP1VyQpy15qWh1Pk= +github.com/aws/aws-sdk-go-v2/service/s3 v1.99.0/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 h1:QKZH0S178gCmFEgst8hN0mCX1KxLgHBKKY/CLqwP8lg= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.9/go.mod h1:7yuQJoT+OoH8aqIxw9vwF+8KpvLZ8AWmvmUWHsGQZvI= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.15 h1:lFd1+ZSEYJZYvv9d6kXzhkZu07si3f+GQ1AaYwa2LUM= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.15/go.mod h1:WSvS1NLr7JaPunCXqpJnWk1Bjo7IxzZXrZi1QQCkuqM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19 h1:dzztQ1YmfPrxdrOiuZRMF6fuOwWlWpD2StNLTceKpys= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19/go.mod h1:YO8TrYtFdl5w/4vmjL8zaBSsiNp3w0L1FfKVKenZT7w= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 h1:p8ogvvLugcR/zLBXTXrTkj0RYBUdErbMnAFFp12Lm/U= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.10/go.mod h1:60dv0eZJfeVXfbT1tFJinbHrDfSJ2GZl4Q//OSSNAVw= +github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= +github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= @@ -53,8 +55,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/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= -github.com/go-co-op/gocron/v2 v2.21.1 h1:QYOK6iOQVCut+jDcs4zRdWRTBHRxRCEeeFi1TnAmgbU= -github.com/go-co-op/gocron/v2 v2.21.1/go.mod h1:5lEiCKk1oVJV39Zg7/YG10OnaVrDAV5GGR6O0663k6U= +github.com/go-co-op/gocron/v2 v2.20.0 h1:9IMrnnVSWjfSh3E54gWmWCHbloQJLh6f9+nwyKfLNpc= +github.com/go-co-op/gocron/v2 v2.20.0/go.mod h1:5lEiCKk1oVJV39Zg7/YG10OnaVrDAV5GGR6O0663k6U= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -63,16 +65,16 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ= github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc= -github.com/gofiber/fiber/v2 v2.52.13 h1:TOKP64iqC9b5P49VrBW5tHhUOvDyrtJ0xePEfzJbCbk= -github.com/gofiber/fiber/v2 v2.52.13/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/gofiber/fiber/v2 v2.52.12 h1:0LdToKclcPOj8PktUdIKo9BUohjjwfnQl42Dhw8/WUw= +github.com/gofiber/fiber/v2 v2.52.12/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA= github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE= 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/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= -github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= -github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= +github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -83,14 +85,14 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= -github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= -github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= -github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= -github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8= -github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= -github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= -github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= +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-runewidth v0.0.22 h1:76lXsPn6FyHtTY+jt2fTTvsMUCZq1k0qwRsAMuxzKAk= +github.com/mattn/go-runewidth v0.0.22/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mattn/go-sqlite3 v1.14.41 h1:8p7Pwz5NHkEbWSqc/ygU4CBGubhFFkpgP9KwcdkAHNA= +github.com/mattn/go-sqlite3 v1.14.41/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= +github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= +github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -100,8 +102,8 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ= -github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= +github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po= +github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.70.0 h1:LAhMGcWk13QZWm85+eg8ZBNbrq5mnkWFGbHMUJHIdXA= @@ -114,12 +116,13 @@ github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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= diff --git a/internal/blog/index.go b/internal/blog/index.go deleted file mode 100644 index 861a90e..0000000 --- a/internal/blog/index.go +++ /dev/null @@ -1,32 +0,0 @@ -package blog - -import "time" - -const IndexFileName = "index.json" - -const IndexSchemaVersion = 1 - -type IndexEntry struct { - Link string `json:"link"` - ModifiedTime time.Time `json:"modifiedTime"` - Title string `json:"title"` - ShortDescription string `json:"shortDescription"` - ActionDate string `json:"actionDate"` - PublishedTime time.Time `json:"publishedTime"` - Thumbnail string `json:"thumbnail"` - Tags []string `json:"tags"` - Geolocation string `json:"geolocation"` - Medley string `json:"medley,omitempty"` - MedleyPart int `json:"medleyPart,omitempty"` -} - -type IndexCategory struct { - GeneratedAt time.Time `json:"generatedAt"` - Pages []IndexEntry `json:"pages"` -} - -type Index struct { - SchemaVersion int `json:"schemaVersion"` - GeneratedAt time.Time `json:"generatedAt"` - Categories map[string]IndexCategory `json:"categories"` -} diff --git a/internal/blog/s3.go b/internal/blog/s3.go index bbd5238..86a6463 100644 --- a/internal/blog/s3.go +++ b/internal/blog/s3.go @@ -2,15 +2,10 @@ package blog import ( "context" - "encoding/json" - "errors" "fmt" "io" - "log/slog" - "net/url" "slices" "strings" - "sync" "time" "github.com/SayaAndy/saya-today-web/config" @@ -19,11 +14,8 @@ import ( awsconfig "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/s3" - s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" ) -const s3ScanConcurrency = 32 - type S3Client struct { prefix string bucketName string @@ -58,7 +50,6 @@ func NewS3Client(cfg *config.StorageConfig) (Client, error) { } s3Opts = append(s3Opts, func(o *s3.Options) { o.UsePathStyle = s3cfg.UsePathStyle - o.DisableLogOutputChecksumValidationSkipped = true }) s3cl := s3.NewFromConfig(awsCfg, s3Opts...) @@ -67,195 +58,79 @@ func NewS3Client(cfg *config.StorageConfig) (Client, error) { } func (c *S3Client) Scan(prefix string) ([]*Page, error) { - pages, err := c.scanFromIndex(prefix) - if err == nil { - return pages, nil - } - - var nsk *s3types.NoSuchKey - if !errors.As(err, &nsk) { - return nil, err - } - - slog.Warn("index.json missing, falling back to listing", slog.String("prefix", c.prefix)) - return c.scanByListing(prefix) -} - -func (c *S3Client) scanFromIndex(prefix string) ([]*Page, error) { - out, err := c.s3cl.GetObject(context.Background(), &s3.GetObjectInput{ - Bucket: aws.String(c.bucketName), - Key: aws.String(IndexFileName), - }) - if err != nil { - return nil, fmt.Errorf("get index.json: %w", err) - } - defer out.Body.Close() - - raw, err := io.ReadAll(out.Body) - if err != nil { - return nil, fmt.Errorf("read index.json: %w", err) - } - - var idx Index - if err := json.Unmarshal(raw, &idx); err != nil { - return nil, fmt.Errorf("unmarshal index.json: %w", err) - } - - wantLang := "" - if i := strings.Index(prefix, "/"); i > 0 { - wantLang = prefix[:i] - } + pages := []*Page{} fullPrefix := c.prefix + prefix - pages := make([]*Page, 0) - for catKey, cat := range idx.Categories { - lang, ok := strings.CutPrefix(catKey, c.prefix) - if !ok { - continue - } - if wantLang != "" && wantLang != lang { - continue - } - for _, e := range cat.Pages { - if !strings.HasPrefix(e.Link, fullPrefix) { - continue - } - linkParts := strings.Split(e.Link, "/") - nameParts := strings.Split(linkParts[len(linkParts)-1], ".") - fileName := strings.Join(nameParts[:len(nameParts)-1], ".") - pages = append(pages, &Page{ - Link: e.Link, - FileName: fileName, - Lang: lang, - ModifiedTime: e.ModifiedTime, - Metadata: &frontmatter.Metadata{ - Title: e.Title, - ShortDescription: e.ShortDescription, - ActionDate: e.ActionDate, - PublishedTime: e.PublishedTime, - Thumbnail: e.Thumbnail, - Tags: e.Tags, - Geolocation: e.Geolocation, - Medley: e.Medley, - MedleyPart: e.MedleyPart, - }, - }) - } + input := &s3.ListObjectsV2Input{ + Bucket: aws.String(c.bucketName), + Prefix: aws.String(fullPrefix), } - return pages, nil -} -func (c *S3Client) scanByListing(prefix string) ([]*Page, error) { - fullPrefix := c.prefix + prefix - - type candidate struct { - key string - lastModified time.Time - } - var candidates []candidate + paginator := s3.NewListObjectsV2Paginator(c.s3cl, input) - paginator := s3.NewListObjectsV2Paginator(c.s3cl, &s3.ListObjectsV2Input{ - Bucket: aws.String(c.bucketName), - Prefix: aws.String(fullPrefix), - }) for paginator.HasMorePages() { output, err := paginator.NextPage(context.Background()) if err != nil { return nil, fmt.Errorf("list S3 objects: %w", err) } + for _, obj := range output.Contents { key := aws.ToString(obj.Key) + if !strings.HasSuffix(key, ".md") { continue } - candidates = append(candidates, candidate{key, aws.ToTime(obj.LastModified)}) - } - } - - pages := make([]*Page, len(candidates)) - sem := make(chan struct{}, s3ScanConcurrency) - var wg sync.WaitGroup - var firstErr error - var errMu sync.Mutex - - for i, cand := range candidates { - sem <- struct{}{} - wg.Go(func() { - defer func() { <-sem }() head, err := c.s3cl.HeadObject(context.Background(), &s3.HeadObjectInput{ Bucket: aws.String(c.bucketName), - Key: aws.String(cand.key), + Key: aws.String(key), }) if err != nil { - errMu.Lock() - if firstErr == nil { - firstErr = fmt.Errorf("head S3 object %s: %w", cand.key, err) - } - errMu.Unlock() - return + return nil, fmt.Errorf("head S3 object %s: %w", key, err) } if head.ContentType == nil || !strings.Contains(*head.ContentType, "text/markdown") { - return + continue } + meta := head.Metadata if meta["title"] == "" { - return + continue } publishedTime, err := time.Parse(time.RFC3339, meta["published-time"]) if err != nil { - errMu.Lock() - if firstErr == nil { - firstErr = fmt.Errorf("failed to parse published time metadata field: %w", err) - } - errMu.Unlock() - return + return nil, fmt.Errorf("failed to parse published time metadata field: %w", err) } - linkParts := strings.Split(cand.key, "/") + linkParts := strings.Split(key, "/") nameParts := strings.Split(linkParts[len(linkParts)-1], ".") fileName := strings.Join(nameParts[:len(nameParts)-1], ".") - lang, _ := strings.CutPrefix(linkParts[0], c.prefix) tags := strings.Split(meta["tags"], ",") slices.Sort(tags) - title, _ := url.QueryUnescape(meta["title"]) - shortDescription, _ := url.QueryUnescape(meta["short-description"]) - thumbnail, _ := url.QueryUnescape(meta["thumbnail"]) + lang, _ := strings.CutPrefix(linkParts[0], c.prefix) - pages[i] = &Page{ - Link: cand.key, + pages = append(pages, &Page{ + Link: key, FileName: fileName, Lang: lang, - ModifiedTime: cand.lastModified, + ModifiedTime: aws.ToTime(obj.LastModified), Metadata: &frontmatter.Metadata{ - Title: title, - ShortDescription: shortDescription, + Title: meta["title"], + ShortDescription: meta["short-description"], ActionDate: meta["action-date"], PublishedTime: publishedTime, - Thumbnail: thumbnail, + Thumbnail: meta["thumbnail"], Tags: tags, Geolocation: meta["geolocation"], }, - } - }) - } - wg.Wait() - - if firstErr != nil { - return nil, firstErr - } - - out := pages[:0] - for _, p := range pages { - if p != nil { - out = append(out, p) + }) } } - return out, nil + + return pages, nil } func (c *S3Client) ReadAll(path string) ([]byte, error) { diff --git a/internal/router/router.go b/internal/router/router.go index 65e35a4..5230e74 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -7,11 +7,8 @@ import ( "fmt" "html/template" "log/slog" - "net" "net/url" - "os" "slices" - "strconv" "strings" "time" @@ -115,7 +112,6 @@ type Router struct { templatedRoutes map[string]map[string]Route templatedPathMatcher *PathMatcher canonicalEndpoint string - endpoint config.EndpointConfig } func NewRouter(cfg *config.Config) (*Router, error) { @@ -260,7 +256,7 @@ func NewRouter(cfg *config.Config) (*Router, error) { templatedRoutes := make(map[string]map[string]Route) templatedPathMatcher := NewPathMatcher() - return &Router{supplements, app, templatedRoutes, templatedPathMatcher, cfg.CanonicalEndpoint, cfg.Endpoint}, nil + return &Router{supplements, app, templatedRoutes, templatedPathMatcher, cfg.CanonicalEndpoint}, nil } func (r *Router) InitRoutes() (err error) { @@ -309,8 +305,6 @@ func (r *Router) InitRoutes() (err error) { cacheKey = fmt.Sprintf("%s.full-page.%s", method, trimmedPath) case ByUrlAndQuery: cacheKey = fmt.Sprintf("%s.full-page.%s.%s", method, trimmedPath, queryString) - case Disabled: - c.Set("Cache-Control", "no-store, no-cache, must-revalidate") } defaultMap := fiber.Map{ @@ -373,7 +367,6 @@ func (r *Router) InitRoutes() (err error) { c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) return c.Status(fiber.ErrBadRequest.Code).SendString(fmt.Sprintf("unknown segment '%s'", part)) } - c.Set("Cache-Control", "no-store, no-cache, must-revalidate") err := r.generalPageSegment(c, part) return err }) @@ -389,30 +382,10 @@ func (r *Router) InitRoutes() (err error) { return nil } -func (r *Router) Listen() error { - switch r.endpoint.Type { - case "unix": - unixConfig := r.endpoint.Config.(*config.UnixConfig) - endpoint, _ := strings.CutPrefix(unixConfig.Path, "unix://") - ln, err := net.Listen("unix", endpoint) - if err != nil { - return fmt.Errorf("error while initializing unix listener: %w", err) - } - chmod, _ := strconv.ParseUint(unixConfig.Chmod[1:], 8, 32) - os.Chmod(unixConfig.Path, os.FileMode(chmod)) - if err := r.app.Listener(ln); err != nil { - return fmt.Errorf("error while running fiber server: %w", err) - } - case "http": - httpConfig := r.endpoint.Config.(*config.HttpConfig) - fmt.Print(httpConfig) - if err := r.app.Listen(httpConfig.ListenOn); err != nil { - return fmt.Errorf("error while running fiber server: %w", err) - } - default: - return fmt.Errorf("error with initializing fiber server: invalid endpoint type (supported are unix and http)") +func (r *Router) Listen(endpoint string) error { + if err := r.app.Listen(endpoint); err != nil { + return fmt.Errorf("error while running fiber server: %w", err) } - return nil } @@ -436,7 +409,6 @@ func (r *Router) Close() (err error) { } slog.Debug("closing page cache") r.supplements.PageCache.Close() - return errors.Join(allErrors...) } diff --git a/locale/localization.en.yaml b/locale/localization.en.yaml index 10bcb94..9923881 100644 --- a/locale/localization.en.yaml +++ b/locale/localization.en.yaml @@ -1,77 +1,77 @@ -TagsLabel: "Tags:" +TagsLabel: 'Tags:' BlogSearch: - Header: "Blog Search" + Header: 'Blog Search' Description: "The Saya Blog posts' list" - TagsHeader: "Tags" - OrderByHeader: "Order by..." - TitleOrdered: "Title" - ActionDateOrdered: "Action Date" - PublicationDateOrdered: "Publication Date" - ChooseAllTags: "Choose All" + TagsHeader: 'Tags' + OrderByHeader: 'Order by...' + TitleOrdered: 'Title' + ActionDateOrdered: 'Action Date' + PublicationDateOrdered: 'Publication Date' + ChooseAllTags: 'Choose All' GlobalMap: - Header: "Global Map" + Header: 'Global Map' Description: "Global map with all the tags of places I've been to" HomePage: - Header: "Home Page" - Contacts: "Contacts" - DidYouKnowThat: "Did you know, that..." - SidebarDescription: "Describing the Sidebar" - HomePageDescription: "Home Page -- where we are right now!" - BlogSearchDescription: "Blog Search. You can find my travel notes there and, maybe, something else!" - MarkerMapDescription: "Map with markers of my travel notes -- a convinient way to find an interesting region and find related posts!" - ThemeSwitchDescription: "Theme Palette lets you change the theme of the site -- there are 2 dark and 2 bright themes currently!" - Hymn1: "We welcome you to Sayasite!" + Header: 'Home Page' + Contacts: 'Contacts' + DidYouKnowThat: 'Did you know, that...' + SidebarDescription: 'Describing the Sidebar' + HomePageDescription: 'Home Page -- where we are right now!' + BlogSearchDescription: 'Blog Search. You can find my travel notes there and, maybe, something else!' + MarkerMapDescription: 'Map with markers of my travel notes -- a convinient way to find an interesting region and find related posts!' + ThemeSwitchDescription: 'Theme Palette lets you change the theme of the site -- there are 2 dark and 2 bright themes currently!' + Hymn1: 'We welcome you to Sayasite!' Hymn2: "We've been expecting you!" - Hymn3: "You bring such joy in Sayasite!" - Hymn4: "No matter where you roam, know our love is true!~" + Hymn3: 'You bring such joy in Sayasite!' + Hymn4: 'No matter where you roam, know our love is true!~' UnsubscribePage: - Header: "Cancelling the Subscription" - UnsetCode: "Your cancel code is not set. You just having a walk around the site?" + Header: 'Cancelling the Subscription' + UnsetCode: 'Your cancel code is not set. You just having a walk around the site?' InvalidCode: "Your cancel code could not be verified. Maybe, the code expired after a day. Or maybe, you've already unsubscribed." OnServerError: "We couldn't unsubscribe you, the problem is on our side! We will solve this problem as soon as possible." - Success: "Cancelled your subscription successfully! We do hope we will engage you again someday." + Success: 'Cancelled your subscription successfully! We do hope we will engage you again someday.' Metadata: - Published: "Published" - Action: "Took place on" + Published: 'Published' + Action: 'Took place on' Mail: - UnsubscribeFooter: "If this letter got you in a bad mood, you can unsubscribe from my blog by {}this link{/}." + UnsubscribeFooter: 'If this letter got you in a bad mood, you can unsubscribe from my blog by {}this link{/}.' VerifyEmail: - Subject: "Verification Link for SAYA.UZ is waiting for you!" - Welcome: "Greetings!" - Intro: "There are 2 ways for us to validate your profile:" - GotoLink: "Go by this link:" + Subject: 'Verification Link for SAYA.TODAY is waiting for you!' + Welcome: 'Greetings!' + Intro: 'There are 2 ways for us to validate your profile:' + GotoLink: 'Go by this link:' InputCode: "Or input this code on the personal page:" - IfRandom: "If you are unsure what this letter this, you can ignore it, and also, if you are interested, you may visit my site :)" + IfRandom: 'If you are unsure what this letter this, you can ignore it, and also, if you are interested, you may visit my site :)' NewPost: - Subject: "A new post arrived at SAYA.UZ!" - Intro: "A new post came!" - CapturedOn: "Captured on" + Subject: 'A new post arrived at SAYA.TODAY!' + Intro: 'A new post came!' + CapturedOn: 'Captured on' UserProfile: - Header: "Personal Settings" - Description: "Set and verify your e-mail here, as well as subscribe to favorite blog tags" - EmailHeader: "E-Mail" - VerificationCodeHeader: "Verification Code" - TypeEmail: "Please type your email" - SendCodeButton: "Send Code" - VerifyButton: "Verify" - VerificationCodeSent: "Verification code sent! Please check your e-mail. Your next verification attempt will be possible on {}." - DelayTilVerification: "You have already requested the verification before! Please wait until {} for a new verification request." - FailedEmailRender: "Failed to render the email status message, please ask administrator for a more detailed cause." - VerificationCodeSendingError: "Failed to send a verification code, please ask administrator for a more detailed cause." - EmailAlreadyValidated: "You already have this E-Mail validated." - EmailTaken: "This E-Mail is already taken." - EmailEmpty: "E-Mail is unset or empty." - VerificationSuccess: "Success! You are now able to subscribe with your new e-mail." - VerificationFailed: "Verification code is invalid, please ask administrator for a more detailed cause." - VerificationEmpty: "Verification code is unset or empty." - SubscriptionHeader: "Subscribe to..." - TagsNone: "None" - TagsAll: "All" - TagsSpecific: "Specific" + Header: 'Personal Settings' + Description: 'Set and verify your e-mail here, as well as subscribe to favorite blog tags' + EmailHeader: 'E-Mail' + VerificationCodeHeader: 'Verification Code' + TypeEmail: 'Please type your email' + SendCodeButton: 'Send Code' + VerifyButton: 'Verify' + VerificationCodeSent: 'Verification code sent! Please check your e-mail. Your next verification attempt will be possible on {}.' + DelayTilVerification: 'You have already requested the verification before! Please wait until {} for a new verification request.' + FailedEmailRender: 'Failed to render the email status message, please ask administrator for a more detailed cause.' + VerificationCodeSendingError: 'Failed to send a verification code, please ask administrator for a more detailed cause.' + EmailAlreadyValidated: 'You already have this E-Mail validated.' + EmailTaken: 'This E-Mail is already taken.' + EmailEmpty: 'E-Mail is unset or empty.' + VerificationSuccess: 'Success! You are now able to subscribe with your new e-mail.' + VerificationFailed: 'Verification code is invalid, please ask administrator for a more detailed cause.' + VerificationEmpty: 'Verification code is unset or empty.' + SubscriptionHeader: 'Subscribe to...' + TagsNone: 'None' + TagsAll: 'All' + TagsSpecific: 'Specific' TagDoesNotExist: 'The tag "{}" does not exist on the site.' TagAlreadyAdded: 'The tag "{}" is already in your subscribed list.' - SaveButton: "Save" - SubscribeInvalidType: "Invalid subscription type." - FailedToSubscribe: "Failed to subscribe by new settings, please ask administrator for a more detailed cause." - SubscribedSuccessfully: "Successfully subscribed you by new settings!" - RefreshPage: "Please refresh the profile page on the device, where you requested the code." + SaveButton: 'Save' + SubscribeInvalidType: 'Invalid subscription type.' + FailedToSubscribe: 'Failed to subscribe by new settings, please ask administrator for a more detailed cause.' + SubscribedSuccessfully: 'Successfully subscribed you by new settings!' + RefreshPage: 'Please refresh the profile page on the device, where you requested the code.' diff --git a/locale/localization.ru.yaml b/locale/localization.ru.yaml index 17e1e41..34e5387 100644 --- a/locale/localization.ru.yaml +++ b/locale/localization.ru.yaml @@ -1,77 +1,77 @@ -TagsLabel: "Тэги:" +TagsLabel: 'Тэги:' BlogSearch: - Header: "Поиск по блогу" - Description: "Каталог записей Saya Blog" - TagsHeader: "Тэги" - OrderByHeader: "Упорядочить по..." - TitleOrdered: "названию" - ActionDateOrdered: "дате действия" - PublicationDateOrdered: "времени публикации" - ChooseAllTags: "Выбрать все" + Header: 'Поиск по блогу' + Description: 'Каталог записей Saya Blog' + TagsHeader: 'Тэги' + OrderByHeader: 'Упорядочить по...' + TitleOrdered: 'названию' + ActionDateOrdered: 'дате действия' + PublicationDateOrdered: 'времени публикации' + ChooseAllTags: 'Выбрать все' GlobalMap: - Header: "Глобальная карта" - Description: "Глобальная карта с метками мест, где я побывал" + Header: 'Глобальная карта' + Description: 'Глобальная карта с метками мест, где я побывал' HomePage: - Header: "Домашняя страница" - Contacts: "Контакты" - DidYouKnowThat: "А вы знали, что..." - SidebarDescription: "Поясняем за сайдбар" - HomePageDescription: "Домашняя страница -- где мы сейчас и находимся!" - BlogSearchDescription: "Поиск по блогу. Там размещены мои путевые заметки и, может, что-то ещё!" - MarkerMapDescription: "Карта с маркерами моих путевых заметок, -- удобный способ найти интересный регион и перейти на заинтересовавшие посты!" - ThemeSwitchDescription: "Позволяет поменять тему сайта -- есть 2 светлые и 2 тёмные темы!" - Hymn1: "Приветствуем Вас на Саясайте!" - Hymn2: "Мы Вас столько ждали!" - Hymn3: "Столько радости от Вас на Саясайте!" - Hymn4: "Где бы Вы не были, знайте, -- наша любовь крепка!" + Header: 'Домашняя страница' + Contacts: 'Контакты' + DidYouKnowThat: 'А вы знали, что...' + SidebarDescription: 'Поясняем за сайдбар' + HomePageDescription: 'Домашняя страница -- где мы сейчас и находимся!' + BlogSearchDescription: 'Поиск по блогу. Там размещены мои путевые заметки и, может, что-то ещё!' + MarkerMapDescription: 'Карта с маркерами моих путевых заметок, -- удобный способ найти интересный регион и перейти на заинтересовавшие посты!' + ThemeSwitchDescription: 'Позволяет поменять тему сайта -- есть 2 светлые и 2 тёмные темы!' + Hymn1: 'Приветствуем Вас на Саясайте!' + Hymn2: 'Мы Вас столько ждали!' + Hymn3: 'Столько радости от Вас на Саясайте!' + Hymn4: 'Где бы Вы не были, знайте, -- наша любовь крепка!' UnsubscribePage: - Header: "Отписка" - UnsetCode: "Код для отписки не проставлен. Вы случайно не заблудились?" - InvalidCode: "Код для отписки не подтверждён. Может, истекло время в сутки для кода. А может, вы уже успели отписаться." - OnServerError: "Мы не смогли вас отписать, проблема на нашей стороне! В скором времени займёмся проблемой." - Success: "Вы отписались от рассылки! Надеюсь, мы ещё сможем вас заинтересовать." + Header: 'Отписка' + UnsetCode: 'Код для отписки не проставлен. Вы случайно не заблудились?' + InvalidCode: 'Код для отписки не подтверждён. Может, истекло время в сутки для кода. А может, вы уже успели отписаться.' + OnServerError: 'Мы не смогли вас отписать, проблема на нашей стороне! В скором времени займёмся проблемой.' + Success: 'Вы отписались от рассылки! Надеюсь, мы ещё сможем вас заинтересовать.' Metadata: - Published: "Опубликовано" - Action: "Время действия" + Published: 'Опубликовано' + Action: 'Время действия' Mail: - UnsubscribeFooter: "Если данное письмо пришло вам случайно, либо вы хотите отписаться, можете перейти по {}этой ссылке{/}." + UnsubscribeFooter: 'Если данное письмо пришло вам случайно, либо вы хотите отписаться, можете перейти по {}этой ссылке{/}.' VerifyEmail: - Subject: "Ссылка для верификации для подтверждения вашей почты SAYA.UZ" - Welcome: "Приветствую!" - Intro: "Есть 2 способа, чтобы подтвердить свою электронную почту:" - GotoLink: "Перейти по этой ссылке:" - InputCode: "Либо ввести этот код на странице подписок:" - IfRandom: "Если ты не понимаешь, что это за спам, можешь проигнорировать это письмо, а ещё, если интересно, можешь посетить мой сайт :)" + Subject: 'Ссылка для верификации для подтверждения вашей почты SAYA.TODAY' + Welcome: 'Приветствую!' + Intro: 'Есть 2 способа, чтобы подтвердить свою электронную почту:' + GotoLink: 'Перейти по этой ссылке:' + InputCode: 'Либо ввести этот код на странице подписок:' + IfRandom: 'Если ты не понимаешь, что это за спам, можешь проигнорировать это письмо, а ещё, если интересно, можешь посетить мой сайт :)' NewPost: - Subject: "Новый пост на SAYA.UZ!" - Intro: "Вышел новый пост!" - CapturedOn: "Снималось" + Subject: 'Новый пост на SAYA.TODAY!' + Intro: 'Вышел новый пост!' + CapturedOn: 'Снималось' UserProfile: - Header: "Личные настройки" - Description: "Здесь можно настроить и верифицировать свою электронную почту, а также подписаться на избранные темы блога" - EmailHeader: "E-Mail" - VerificationCodeHeader: "Код верификации" - TypeEmail: "Пожалуйста, введите свою почту" - SendCodeButton: "Отправить код" - VerifyButton: "Верифицировать" - VerificationCodeSent: "Выслали вам код верификации! Проверьте свою почту. Следующая попытка верификации будет возможна {}." - DelayTilVerification: "Вы уже запрашивали недавно верификацию! Дождитесь {} для новой попытки." - FailedEmailRender: "Неудачная попытка создать электронное письмо, более подробно спрашивайте у администратора." - VerificationCodeSendingError: "Неудачная попытка прислать код для верификации, более подробно спрашивайте у администратора." - EmailAlreadyValidated: "Эту почту вы уже подтверждали." - EmailTaken: "Эта почта уже занята." - EmailEmpty: "Почта не введена." - VerificationSuccess: "Поздравляем! Теперь по этой почте можно подписаться на блог." - VerificationFailed: "Код для верификации невалиден, при вопросах, пожалуйста, обращайтесь к администратору." - VerificationEmpty: "Код для верификации не введён." - SubscriptionHeader: "Подписаться на..." - TagsNone: "Ничего" - TagsAll: "Всё" - TagsSpecific: "Определённое" + Header: 'Личные настройки' + Description: 'Здесь можно настроить и верифицировать свою электронную почту, а также подписаться на избранные темы блога' + EmailHeader: 'E-Mail' + VerificationCodeHeader: 'Код верификации' + TypeEmail: 'Пожалуйста, введите свою почту' + SendCodeButton: 'Отправить код' + VerifyButton: 'Верифицировать' + VerificationCodeSent: 'Выслали вам код верификации! Проверьте свою почту. Следующая попытка верификации будет возможна {}.' + DelayTilVerification: 'Вы уже запрашивали недавно верификацию! Дождитесь {} для новой попытки.' + FailedEmailRender: 'Неудачная попытка создать электронное письмо, более подробно спрашивайте у администратора.' + VerificationCodeSendingError: 'Неудачная попытка прислать код для верификации, более подробно спрашивайте у администратора.' + EmailAlreadyValidated: 'Эту почту вы уже подтверждали.' + EmailTaken: 'Эта почта уже занята.' + EmailEmpty: 'Почта не введена.' + VerificationSuccess: 'Поздравляем! Теперь по этой почте можно подписаться на блог.' + VerificationFailed: 'Код для верификации невалиден, при вопросах, пожалуйста, обращайтесь к администратору.' + VerificationEmpty: 'Код для верификации не введён.' + SubscriptionHeader: 'Подписаться на...' + TagsNone: 'Ничего' + TagsAll: 'Всё' + TagsSpecific: 'Определённое' TagDoesNotExist: 'Тэга "{}" нет на сайте.' TagAlreadyAdded: 'Тэг "{}" уже у вас добавлен.' - SaveButton: "Сохранить" - SubscribeInvalidType: "Неизвестный тип подписки." - FailedToSubscribe: "Неудачная попытка записать новую подписку, более подробно спрашивайте у администратора." - SubscribedSuccessfully: "Вы успешно подписались по новым настройкам!" - RefreshPage: "Пожалуйста, обновите страницу с профилем на устройстве, откуда запрашивали код." + SaveButton: 'Сохранить' + SubscribeInvalidType: 'Неизвестный тип подписки.' + FailedToSubscribe: 'Неудачная попытка записать новую подписку, более подробно спрашивайте у администратора.' + SubscribedSuccessfully: 'Вы успешно подписались по новым настройкам!' + RefreshPage: 'Пожалуйста, обновите страницу с профилем на устройстве, откуда запрашивали код.' @@ -47,7 +47,7 @@ func main() { routerShutdown := make(chan struct{}, 1) go func() { - if err := app.Listen(); err != nil { + if err := app.Listen(":3000"); err != nil { slog.Error("error while running fiber server", slog.String("error", err.Error())) if nestedErr := app.Close(); nestedErr != nil { slog.Error("error while shutting down fiber server", slog.String("error", err.Error())) diff --git a/static/input.css b/static/input.css index cdab1f0..75482d8 100644 --- a/static/input.css +++ b/static/input.css @@ -1,3 +1,5 @@ +@import url('https://fonts.googleapis.com/css2?family=Gentium+Plus:ital,wght@0,400;0,700;1,400;1,700&family=M+PLUS+Rounded+1c:wght@100;300;400;500;700;800;900&display=swap'); + @import "tailwindcss"; @plugin "@tailwindcss/forms"; diff --git a/views/layouts/general-mail.html b/views/layouts/general-mail.html index 452982a..00bd964 100644 --- a/views/layouts/general-mail.html +++ b/views/layouts/general-mail.html @@ -1,119 +1,99 @@ -<!doctype html> +<!DOCTYPE html> <html lang="en"> - <meta charset="UTF-8" /> + <meta charset="UTF-8"> <head> <style> - @font-face { - font-family: "Gentium Plus"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: - local("Gentium Plus"), - url(https://fonts.gstatic.com/s/gentiumplus/v2/Iurd6Ytw-oSPaZ00r2bNe8VZj5FNym499r7e.woff2) - format("woff2"); - unicode-range: - U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; - } - @font-face { - font-family: "Gentium Plus"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: - local("Gentium Plus"), - url(https://fonts.gstatic.com/s/gentiumplus/v2/Iurd6Ytw-oSPaZ00r2bNe8VZi5FNym499g.woff2) - format("woff2"); - unicode-range: - U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, - U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, - U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; - } - html { - font-family: "Gentium Plus", sans-serif; - --bg-paper: url(data:image/webp;base64,UklGRi4BAABXRUJQVlA4ICIBAABQCQCdASqAAIAAPm02lUmkIqIkIImIgA2JaW3rMFmGDAOKLAoJtP591ByFR74Tun2DP+Lygpfsz5FH7+cKgxi2Islfd03i5AJdXUfU0SDc8SXYAAD++hlzeYAJWQEF8ZdkJdlcA+5btZg0FMGaso8SHoBph0qAOQysElJ5C5SJFNoKIV1J550UyL7Y9Gh0/c/uzLnBmr8lvz7k1WeA81H4S5ViU/wBqa7aM1m8D9rDtrk8cSTqx+xWF6MS8UfZGSsks6CT/igtgv6syFN9uNA3LjnjiCJKYOvFcMkX+jB2578GWVo3nyLaM1QNPYCxn3TiUbnf7U0MdD2NsBpg1+Jd1wu6yW/U+goN+IQS6Nt4RMWltVcZHlxrneKJi0SobO0AAA==); - } - body { - background-color: #e3e6ff; - } - a { - text-decoration: underline; - text-shadow: none !important; - } - .header { - width: 100%; - padding: 8px 8px 8px 32px; - font-size: 48px; - color: white; - text-shadow: - -2px 0 #343b58, - 0 2px #343b58, - 2px 0 #343b58, - 0 -2px #343b58; - background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='40' height='80'><path fill='#e5e8ff' d='M0 0h40v80H0z'/><g fill='none' stroke-linecap='square' stroke-width='4.5'><path stroke='#8d4453' d='m-10 5 20 10L30 5l20 10'/><path stroke='#375e0d' d='m-10 25 20 10 20-10 20 10'/><path stroke='#006a85' d='m-10 45 20 10 20-10 20 10'/><path stroke='#5a3e8e' d='m-10 65 20 10 20-10 20 10'/></g></svg>"); - background-color: #e3e6ff; - background-size: contain; - background-repeat: repeat-x; - } - .content { - margin: 0 32px 0 32px; - padding: 16px 16px 32px 16px; - font-size: 24px; - text-shadow: - -1px 0 #343b58, - 0 1px #343b58, - 1px 0 #343b58, - 0 -1px #343b58; - color: white; - background-color: #c3caff; - background-image: var(--bg-paper); - background-blend-mode: multiply; - background-repeat: repeat; - background-size: 128px; - } - .footer { - margin: 0; - padding: 1px 8px 1px 8px; - font-size: 16px; - color: white; - } - .darkened { - background-color: #9aa5f4; - background-image: var(--bg-paper); - background-blend-mode: multiply; - background-repeat: repeat; - background-size: 128px; - } - .outlier { - padding-inline: 4px; - background-color: #6c6e75; - background-image: var(--bg-paper); - background-blend-mode: multiply; - background-repeat: repeat; - background-size: 128px; - } - table, - th, - td { - border: 1px solid black; - } - td { - padding: 5px; - } - img { - width: 200px; - } +@font-face { + font-family: 'Gentium Plus'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: local('Gentium Plus'), + url(https://fonts.gstatic.com/s/gentiumplus/v2/Iurd6Ytw-oSPaZ00r2bNe8VZj5FNym499r7e.woff2) format('woff2'); + unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +@font-face { + font-family: 'Gentium Plus'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: local('Gentium Plus'), + url(https://fonts.gstatic.com/s/gentiumplus/v2/Iurd6Ytw-oSPaZ00r2bNe8VZi5FNym499g.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +html { + font-family: 'Gentium Plus', sans-serif; + --bg-paper: url(data:image/webp;base64,UklGRi4BAABXRUJQVlA4ICIBAABQCQCdASqAAIAAPm02lUmkIqIkIImIgA2JaW3rMFmGDAOKLAoJtP591ByFR74Tun2DP+Lygpfsz5FH7+cKgxi2Islfd03i5AJdXUfU0SDc8SXYAAD++hlzeYAJWQEF8ZdkJdlcA+5btZg0FMGaso8SHoBph0qAOQysElJ5C5SJFNoKIV1J550UyL7Y9Gh0/c/uzLnBmr8lvz7k1WeA81H4S5ViU/wBqa7aM1m8D9rDtrk8cSTqx+xWF6MS8UfZGSsks6CT/igtgv6syFN9uNA3LjnjiCJKYOvFcMkX+jB2578GWVo3nyLaM1QNPYCxn3TiUbnf7U0MdD2NsBpg1+Jd1wu6yW/U+goN+IQS6Nt4RMWltVcZHlxrneKJi0SobO0AAA==); +} +body { + background-color: #e3e6ff; +} +a { + text-decoration: underline; + text-shadow: none !important; +} +.header { + width: 100%; + padding: 8px 8px 8px 32px; + font-size: 48px; + color: white; + text-shadow: -2px 0 #343b58, 0 2px #343b58, 2px 0 #343b58, 0 -2px #343b58; + background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='40' height='80'><path fill='#e5e8ff' d='M0 0h40v80H0z'/><g fill='none' stroke-linecap='square' stroke-width='4.5'><path stroke='#8d4453' d='m-10 5 20 10L30 5l20 10'/><path stroke='#375e0d' d='m-10 25 20 10 20-10 20 10'/><path stroke='#006a85' d='m-10 45 20 10 20-10 20 10'/><path stroke='#5a3e8e' d='m-10 65 20 10 20-10 20 10'/></g></svg>"); + background-color: #e3e6ff; + background-size: contain; + background-repeat: repeat-x; +} +.content { + margin: 0 32px 0 32px; + padding: 16px 16px 32px 16px; + font-size: 24px; + text-shadow: -1px 0 #343b58, 0 1px #343b58, 1px 0 #343b58, 0 -1px #343b58; + color: white; + background-color: #c3caff; + background-image: var(--bg-paper); + background-blend-mode: multiply; + background-repeat: repeat; + background-size: 128px; +} +.footer { + margin: 0; + padding: 1px 8px 1px 8px; + font-size: 16px; + color: white; +} +.darkened { + background-color: #9aa5f4; + background-image: var(--bg-paper); + background-blend-mode: multiply; + background-repeat: repeat; + background-size: 128px; +} +.outlier { + padding-inline: 4px; + background-color: #6c6e75; + background-image: var(--bg-paper); + background-blend-mode: multiply; + background-repeat: repeat; + background-size: 128px; +} +table,th,td { + border: 1px solid black; +} +td { + padding: 5px; +} +img { + width: 200px; +} </style> </head> <body> - <div id="header" class="header">saya.uz</div> + <div id="header" class="header"> + saya.today + </div> - <div - id="content" - class="content" - style="background-color: #e3e6ff !important" - > + <div id="content" class="content" style="background-color: #e3e6ff !important;"> {{ block "body" . }}{{ end }} </div> diff --git a/views/layouts/general-page.html b/views/layouts/general-page.html index 2f949a3..93567d3 100644 --- a/views/layouts/general-page.html +++ b/views/layouts/general-page.html @@ -12,13 +12,12 @@ <link rel="canonical" href="{{.Content}}"> {{- end }} {{- end }} - <link rel="stylesheet" href="{{ .StaticStorageBaseUrl }}/fonts/fonts.css"> <link rel="stylesheet" href="{{ .StaticStorageBaseUrl }}/libs/font-awesome/7.2.0/css/fontawesome.min.css"> <link rel="stylesheet" href="{{ .StaticStorageBaseUrl }}/libs/font-awesome/7.2.0/css/brands.min.css"> <link rel="stylesheet" href="{{ .StaticStorageBaseUrl }}/libs/font-awesome/7.2.0/css/solid.min.css"> <link rel="stylesheet" href="{{ .StaticStorageBaseUrl }}/libs/leaflet/1.9.4/leaflet.css"> <link rel="stylesheet" href="{{ .StaticStorageBaseUrl }}/libs/glightbox/3.3.0/css/glightbox.min.css"> - <link rel="stylesheet" href="/output.css"> + <link href="/output.css" rel="stylesheet"> {{- with .LinkedData }} <script type="application/ld+json"> {{ . }} diff --git a/views/pages/global-map.html b/views/pages/global-map.html index bb51131..2459ca1 100644 --- a/views/pages/global-map.html +++ b/views/pages/global-map.html @@ -1,413 +1,306 @@ -<!doctype html> +<!DOCTYPE html> <html lang="en"> - <head> - <meta charset="UTF-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <meta name="description" content="{{ .L.GlobalMap.Description }}" /> - <meta property="og:title" content="{{ .L.GlobalMap.Header }}" /> - <meta - property="og:description" - content="{{ .L.GlobalMap.Description }}" - /> - <meta - property="og:url" - content="{{ .CanonicalEndpoint }}/{{ .Lang }}/map" - /> - <meta property="og:type" content="site" /> - <title>{{ .L.GlobalMap.Header }} // SAYA.UZ</title> - <link rel="canonical" href="{{ .CanonicalEndpoint }}/{{ .Lang }}/map"> - <link rel="stylesheet" href="{{ .StaticStorageBaseUrl }}/fonts/fonts.css"> - <link - rel="stylesheet" - href="{{ .StaticStorageBaseUrl }}/libs/font-awesome/7.2.0/css/fontawesome.min.css" - /> - <link - rel="stylesheet" - href="{{ .StaticStorageBaseUrl }}/libs/font-awesome/7.2.0/css/brands.min.css" - /> - <link - rel="stylesheet" - href="{{ .StaticStorageBaseUrl }}/libs/font-awesome/7.2.0/css/solid.min.css" - /> - {{ block "header" . }}{{ end }} - <link rel="stylesheet" href="/output.css"> - <link - rel="stylesheet" - href="{{ .StaticStorageBaseUrl }}/libs/leaflet/1.9.4/leaflet.css" - /> - <link - rel="stylesheet" - href="{{ .StaticStorageBaseUrl }}/libs/leaflet.markercluster/1.4.1/MarkerCluster.css" - /> - <link - rel="stylesheet" - href="{{ .StaticStorageBaseUrl }}/libs/leaflet.markercluster/1.4.1/MarkerCluster.Default.css" - /> - </head> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="{{ .L.GlobalMap.Description }}"> + <meta property="og:title" content="{{ .L.GlobalMap.Header }}"> + <meta property="og:description" content="{{ .L.GlobalMap.Description }}"> + <meta property="og:url" content="{{ .CanonicalEndpoint }}/{{ .Lang }}/map"> + <meta property="og:type" content="site"> + <title>{{ .L.GlobalMap.Header }} // SAYA TODAY</title> + <link rel="canonical" href="{{ .CanonicalEndpoint }}/{{ .Lang }}/map"> + <link rel="stylesheet" href="{{ .StaticStorageBaseUrl }}/libs/font-awesome/7.2.0/css/fontawesome.min.css"> + <link rel="stylesheet" href="{{ .StaticStorageBaseUrl }}/libs/font-awesome/7.2.0/css/brands.min.css"> + <link rel="stylesheet" href="{{ .StaticStorageBaseUrl }}/libs/font-awesome/7.2.0/css/solid.min.css"> + {{ block "header" . }}{{ end }} + <link href="/output.css" rel="stylesheet"> + <link rel="stylesheet" href="{{ .StaticStorageBaseUrl }}/libs/leaflet/1.9.4/leaflet.css"> + <link rel="stylesheet" href="{{ .StaticStorageBaseUrl }}/libs/leaflet.markercluster/1.4.1/MarkerCluster.css"> + <link rel="stylesheet" href="{{ .StaticStorageBaseUrl }}/libs/leaflet.markercluster/1.4.1/MarkerCluster.Default.css"> +</head> - <body - data-theme="ram" - class="font-m-plus text-main-hard overflow-hidden bg-interlocked-hexagons h-dvh" - > - <script> - function switchTheme(theme) { - if (theme == "" || typeof theme == "undefined") { - const currentTheme = - document.body.getAttribute("data-theme"); - switch (currentTheme) { - case "olive": - theme = "lettuce"; - break; - case "lettuce": - theme = "night"; - break; - case "night": - theme = "ram"; - break; - case "ram": - theme = "lilac"; - break; - case "lilac": - theme = "olive"; - break; - } +<body data-theme="ram" class="font-m-plus text-main-hard overflow-hidden bg-interlocked-hexagons h-dvh"> + + <script> + function switchTheme(theme) { + if (theme == "" || typeof theme == "undefined") { + const currentTheme = document.body.getAttribute('data-theme'); + switch (currentTheme) { + case "olive": theme = "lettuce"; break; + case "lettuce": theme = "night"; break; + case "night": theme = "ram"; break; + case "ram": theme = "lilac"; break; + case "lilac": theme = "olive"; break; } - localStorage.setItem("theme", theme); - document.body.setAttribute("data-theme", theme); } + localStorage.setItem('theme', theme); + document.body.setAttribute('data-theme', theme); + } - const savedTheme = localStorage.getItem("theme") || "lilac"; - switchTheme(savedTheme); - </script> + const savedTheme = localStorage.getItem('theme') || 'lilac'; + switchTheme(savedTheme); + </script> - <script> - const clientTimeZone = - Intl.DateTimeFormat().resolvedOptions().timeZone; + <script> + const clientTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; - function convertRemToPixels(rem) { - return ( - rem * - parseFloat( - getComputedStyle(document.documentElement).fontSize, - ) - ); - } - </script> + function convertRemToPixels(rem) { + return rem * parseFloat(getComputedStyle(document.documentElement).fontSize); + } + </script> - <div - id="sidebar" - class="bg-sidebar block fixed shadow-[0_0_0.4rem_black] z-1001 w-fit h-auto" - > - <div class="text-[3rem] flex flex-col gap-0 relative z-1001"> - <a - href="./" - class="themed-button themed-button-sidebar w-fit h-fit" - draggable="false" - > - <i - class="fas fa-circle-left flex faw-18 h-18 content-center text-center text-sidebar" - ></i> - </a> - <a - href="/{{ .Lang }}" - class="themed-button themed-button-sidebar w-fit h-fit" - draggable="false" - > - <i - class="fas fa-home flex faw-18 h-18 content-center text-center text-sidebar" - ></i> - </a> - <a - href="/{{ .Lang }}/blog" - class="themed-button themed-button-sidebar w-fit h-fit" - draggable="false" - > - <i - class="fas fa-search flex faw-18 h-18 content-center text-center text-sidebar" - ></i> - </a> - <a - href="/{{ .Lang }}/map" - class="themed-button themed-button-sidebar w-fit h-fit" - draggable="false" - > - <i - class="fas fa-map flex faw-18 h-18 content-center text-center text-sidebar" - ></i> - </a> - <div - id="toolbar-theme-button" - onclick="toggleThemeWheel()" - class="themed-button themed-button-sidebar w-fit h-fit" - draggable="false" - > - <i - id="toolbar-theme-button-content" - class="fas fa-swatchbook flex faw-18 h-18 content-center text-center text-sidebar" - ></i> - </div> - <a - href="/{{ .Lang }}/user" - id="sidebar-user-button" - class="themed-button themed-button-sidebar w-fit h-fit" - draggable="false" - > - <i - onclick="return changeUrl('/{{ .Lang }}/user');" - class="fas fa-user flex faw-18 h-18 content-center text-center text-sidebar" - ></i> - </a> - </div> - <div class="theme-wheel" id="theme-wheel"> - <div class="theme-icon" data-theme="olive"></div> - <div class="theme-icon" data-theme="lettuce"></div> - <div class="theme-icon" data-theme="night"></div> - <div class="theme-icon" data-theme="ram"></div> - <div class="theme-icon" data-theme="lilac"></div> - <div class="theme-pin"></div> + <div id="sidebar" class="bg-sidebar block fixed shadow-[0_0_0.4rem_black] z-1001 w-fit h-auto"> + <div class="text-[3rem] flex flex-col gap-0 relative z-1001"> + <a href="./" class="themed-button themed-button-sidebar w-fit h-fit" draggable="false"> + <i class="fas fa-circle-left flex faw-18 h-18 content-center text-center text-sidebar"></i> + </a> + <a href="/{{ .Lang }}" class="themed-button themed-button-sidebar w-fit h-fit" draggable="false"> + <i class="fas fa-home flex faw-18 h-18 content-center text-center text-sidebar"></i> + </a> + <a href="/{{ .Lang }}/blog" class="themed-button themed-button-sidebar w-fit h-fit" draggable="false"> + <i class="fas fa-search flex faw-18 h-18 content-center text-center text-sidebar"></i> + </a> + <a href="/{{ .Lang }}/map" class="themed-button themed-button-sidebar w-fit h-fit" draggable="false"> + <i class="fas fa-map flex faw-18 h-18 content-center text-center text-sidebar"></i> + </a> + <div id="toolbar-theme-button" onclick="toggleThemeWheel();" class="themed-button themed-button-sidebar w-fit h-fit" draggable="false"> + <i id="toolbar-theme-button-content" class="fas fa-swatchbook flex faw-18 h-18 content-center text-center text-sidebar"></i> </div> + <a href="/{{ .Lang }}/user" id="sidebar-user-button" class="themed-button themed-button-sidebar w-fit h-fit" draggable="false"> + <i onclick="return changeUrl('/{{ .Lang }}/user');" class="fas fa-user flex faw-18 h-18 content-center text-center text-sidebar"></i> + </a> </div> + <div class="theme-wheel" id="theme-wheel"> + <div class="theme-icon" data-theme="olive"></div> + <div class="theme-icon" data-theme="lettuce"></div> + <div class="theme-icon" data-theme="night"></div> + <div class="theme-icon" data-theme="ram"></div> + <div class="theme-icon" data-theme="lilac"></div> + <div class="theme-pin"></div> + </div> + </div> - <div - id="map-container" - class="bg-paper bg-background-dark flex z-10 w-full h-full" - ></div> - - <script src="{{ .StaticStorageBaseUrl }}/libs/htmx/2.0.6/htmx.min.js"></script> - <script src="{{ .StaticStorageBaseUrl }}/libs/leaflet/1.9.4/leaflet.js"></script> - <script src="{{ .StaticStorageBaseUrl }}/libs/leaflet.markercluster/1.4.1/leaflet.markercluster.js"></script> - - <script> - const themeWheel = document.getElementById("theme-wheel"); - const themeButton = document.getElementById("toolbar-theme-button"); - const themeIcons = document.querySelectorAll(".theme-icon"); - const themeWheelRadius = convertRemToPixels(6); - const THUMB_BASE = "{{ .ThumbnailBaseUrl }}"; + <div id="map-container" class="bg-paper bg-background-dark flex z-10 w-full h-full"></div> - themeIcons.forEach((icon) => { - if (icon.getAttribute("data-theme") == savedTheme) { - icon.classList.add("active"); - } - }); + <script src="{{ .StaticStorageBaseUrl }}/libs/htmx/2.0.6/htmx.min.js"></script> + <script src="{{ .StaticStorageBaseUrl }}/libs/leaflet/1.9.4/leaflet.js"></script> + <script src="{{ .StaticStorageBaseUrl }}/libs/leaflet.markercluster/1.4.1/leaflet.markercluster.js"></script> - function calculateVmax(percent) { - const viewportWidth = window.innerWidth; - const viewportHeight = window.innerHeight; + <script> + const themeWheel = document.getElementById('theme-wheel'); + const themeButton = document.getElementById('toolbar-theme-button'); + const themeIcons = document.querySelectorAll('.theme-icon'); + const themeWheelRadius = convertRemToPixels(6); + const THUMB_BASE = "{{ .ThumbnailBaseUrl }}"; - const largerDimension = Math.max(viewportWidth, viewportHeight); + themeIcons.forEach(icon => { + if (icon.getAttribute('data-theme') == savedTheme) { + icon.classList.add('active'); + } + }); - const vmaxValue = (largerDimension / 100) * percent; + function calculateVmax(percent) { + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; - return vmaxValue; - } + const largerDimension = Math.max(viewportWidth, viewportHeight); - function positionThemeIcons() { - const sliceAngle = 360 / themeIcons.length; + const vmaxValue = (largerDimension / 100) * percent; - themeIcons.forEach((icon, index) => { - const startAngle = index * sliceAngle; - const endAngle = (index + 1) * sliceAngle; + return vmaxValue; + } - const clipPath = createSliceClipPath( - startAngle, - endAngle, - themeWheelRadius, - ); - icon.style.clipPath = clipPath; - }); - } + function positionThemeIcons() { + const sliceAngle = 360 / themeIcons.length; - function createSliceClipPath(startAngle, endAngle, radius) { - const centerX = radius; - const centerY = radius; + themeIcons.forEach((icon, index) => { + const startAngle = index * sliceAngle; + const endAngle = (index + 1) * sliceAngle; - let points = `${centerX}px ${centerY}px`; + const clipPath = createSliceClipPath(startAngle, endAngle, themeWheelRadius); + icon.style.clipPath = clipPath; + }); + } - const segments = Math.max( - 2, - Math.ceil(Math.abs(endAngle - startAngle) / 5), - ); - const angleStep = (endAngle - startAngle) / segments; + function createSliceClipPath(startAngle, endAngle, radius) { + const centerX = radius; + const centerY = radius; - for (let i = 0; i <= segments; i++) { - const angle = - ((startAngle + angleStep * i - 90) * Math.PI) / 180; - const x = centerX + Math.cos(angle) * radius; - const y = centerY + Math.sin(angle) * radius; - points += `, ${x}px ${y}px`; - } + let points = `${centerX}px ${centerY}px`; - points += `, ${centerX}px ${centerY}px`; + const segments = Math.max(2, Math.ceil(Math.abs(endAngle - startAngle) / 5)); + const angleStep = (endAngle - startAngle) / segments; - return `polygon(${points})`; + for (let i = 0; i <= segments; i++) { + const angle = ((startAngle + angleStep * i - 90) * Math.PI) / 180; + const x = centerX + Math.cos(angle) * radius; + const y = centerY + Math.sin(angle) * radius; + points += `, ${x}px ${y}px`; } - function positionThemeWheel() { - const buttonRect = themeButton.getBoundingClientRect(); - const buttonCenterX = buttonRect.left + buttonRect.width / 2; - const buttonCenterY = buttonRect.top + buttonRect.height / 2; + points += `, ${centerX}px ${centerY}px`; + + return `polygon(${points})`; + } - const margin = 0; + function positionThemeWheel() { + const buttonRect = themeButton.getBoundingClientRect(); + const buttonCenterX = buttonRect.left + buttonRect.width / 2; + const buttonCenterY = buttonRect.top + buttonRect.height / 2; - let wheelX = buttonCenterX - themeWheelRadius; - let wheelY = buttonCenterY - themeWheelRadius; + const margin = 0; - const viewportWidth = window.innerWidth; - const viewportHeight = window.innerHeight; + let wheelX = buttonCenterX - themeWheelRadius; + let wheelY = buttonCenterY - themeWheelRadius; - if (wheelX < margin) { - wheelX = margin; - } else if ( - wheelX + themeWheelRadius * 2 > - viewportWidth - margin - ) { - wheelX = viewportWidth - themeWheelRadius * 2 - margin; - } + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; - if (wheelY < margin) { - wheelY = margin; - } else if ( - wheelY + themeWheelRadius * 2 > - viewportHeight - margin - ) { - wheelY = viewportHeight - themeWheelRadius * 2 - margin; - } - - themeWheel.style.left = wheelX + "px"; - themeWheel.style.top = wheelY + "px"; + if (wheelX < margin) { + wheelX = margin; + } else if (wheelX + (themeWheelRadius * 2) > viewportWidth - margin) { + wheelX = viewportWidth - (themeWheelRadius * 2) - margin; } - function hideThemeWheel() { - themeWheel.classList.remove("active"); + if (wheelY < margin) { + wheelY = margin; + } else if (wheelY + (themeWheelRadius * 2) > viewportHeight - margin) { + wheelY = viewportHeight - (themeWheelRadius * 2) - margin; } - function showThemeWheel() { - positionThemeWheel(); - positionThemeIcons(); - themeWheel.classList.add("active"); - } + themeWheel.style.left = wheelX + 'px'; + themeWheel.style.top = wheelY + 'px'; + } - function toggleThemeWheel() { - if (themeWheel.classList.contains("active")) { - hideThemeWheel(); - } else { - showThemeWheel(); - } + function hideThemeWheel() { + themeWheel.classList.remove('active'); + } + + function showThemeWheel() { + positionThemeWheel(); + positionThemeIcons(); + themeWheel.classList.add('active'); + } + + function toggleThemeWheel() { + if (themeWheel.classList.contains('active')) { + hideThemeWheel(); + } else { + showThemeWheel(); } + } - themeIcons.forEach((icon) => { - icon.addEventListener("click", (e) => { - e.stopPropagation(); - const theme = icon.getAttribute("data-theme"); - switchTheme(theme); - themeIcons.forEach((icon) => { - icon.classList.remove("active"); - }); - icon.classList.add("active"); - }); + themeIcons.forEach(icon => { + icon.addEventListener('click', (e) => { + e.stopPropagation(); + const theme = icon.getAttribute('data-theme'); + switchTheme(theme); + themeIcons.forEach(icon => { + icon.classList.remove('active'); + }) + icon.classList.add('active'); }); + }); - themeWheel.addEventListener("mouseleave", hideThemeWheel); + themeWheel.addEventListener('mouseleave', hideThemeWheel); - document.addEventListener("click", (e) => { - if ( - !themeWheel.contains(e.target) && - e.target !== themeButton && - !e.target.classList.contains("toolbar-theme-button-content") - ) { - hideThemeWheel(); - } - }); - </script> + document.addEventListener('click', (e) => { + if (!themeWheel.contains(e.target) && e.target !== themeButton && !e.target.classList.contains("toolbar-theme-button-content")) { + hideThemeWheel(); + } + }); + </script> - <script> - var map; - var markers = L.markerClusterGroup(); + <script> + var map; + var markers = L.markerClusterGroup(); - function createCustomIcon(color, borderColor = "var(--color-main-soft)") { - const svgIcon = ` - <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> - <path d="M12 1C7.5 1 4 4.5 4 9c0 6.5 8 14 8 14s8-7.5 8-14c0-4.5-3.5-8-8-8zm0 11c-1.5 0-3-1.5-3-3s1.5-3 3-3 3 1.5 3 3-1.5 3-3 3z" - fill="${color}" - stroke="${borderColor}" - stroke-width="1" - stroke-linejoin="round"/> - </svg> - `; + function createCustomIcon(color, borderColor = "var(--color-main-soft)") { + const svgIcon = ` + <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <path d="M12 1C7.5 1 4 4.5 4 9c0 6.5 8 14 8 14s8-7.5 8-14c0-4.5-3.5-8-8-8zm0 11c-1.5 0-3-1.5-3-3s1.5-3 3-3 3 1.5 3 3-1.5 3-3 3z" + fill="${color}" + stroke="${borderColor}" + stroke-width="1" + stroke-linejoin="round"/> + </svg> + `; - return L.divIcon({ - html: svgIcon, - className: 'custom-svg-marker', - iconSize: [32, 32], - iconAnchor: [16, 32], - popupAnchor: [0, -32] - }); - } + return L.divIcon({ + html: svgIcon, + className: 'custom-svg-marker', + iconSize: [32, 32], + iconAnchor: [16, 32], + popupAnchor: [0, -32] + }); + } - function initLocationMap() { - map = L.map('map-container').setView([{{ .MapLocationLat }}, {{ .MapLocationLong }}], 4); + function initLocationMap() { + map = L.map('map-container').setView([{{ .MapLocationLat }}, {{ .MapLocationLong }}], 4); - L.tileLayer('https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png', { - maxZoom: 20, - attribution: '<a href="https://github.com/cyclosm/cyclosm-cartocss-style/releases" title="CyclOSM - Open Bicycle render">CyclOSM</a> | Map data: © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', - zoomControl: false - }).addTo(map); + L.tileLayer('https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png', { + maxZoom: 20, + attribution: '<a href="https://github.com/cyclosm/cyclosm-cartocss-style/releases" title="CyclOSM - Open Bicycle render">CyclOSM</a> | Map data: © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', + zoomControl: false + }).addTo(map); - L.control.zoom({ - position: 'topright' - }).addTo(map); + L.control.zoom({ + position: 'topright' + }).addTo(map); - map.addLayer(markers); + map.addLayer(markers); + } + + function markerToHsl(str, newCoef) { + let hash = 0; + for (let i = 0; i < str.length / 2; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + hash = str.charCodeAt(str.length - i - 1) + ((hash << 5) - hash); + hash %= 360; } - function markerToHsl(str, newCoef) { - let hash = 0; - for (let i = 0; i < str.length / 2; i++) { - hash = str.charCodeAt(i) + ((hash << 5) - hash); - hash = str.charCodeAt(str.length - i - 1) + ((hash << 5) - hash); - hash %= 360; - } + return [hash, (20 + newCoef * 80).toFixed(0), (20 + newCoef * 75).toFixed(0)]; + } + + function mapAddMarker(x, y, relativeErrorMeters = 0, title = "", link = "", thumbnail = "", newCoef = 1) { + const [h, s, l] = markerToHsl(title, newCoef); + const color = `hsl(${h} ${s}% ${l}%)`; - return [hash, (20 + newCoef * 80).toFixed(0), (20 + newCoef * 75).toFixed(0)]; + if (relativeErrorMeters != 0) { + var circle = L.circle([x, y], { + color: color, + fillColor: color, + fillOpacity: 0.15, + radius: relativeErrorMeters + }).addTo(map); } - function mapAddMarker(x, y, relativeErrorMeters = 0, title = "", link = "", thumbnail = "", newCoef = 1) { - const [h, s, l] = markerToHsl(title, newCoef); - const color = `hsl(${h} ${s}% ${l}%)`; + var marker = L.marker([x, y], { + icon: createCustomIcon(color), + title: title, + }).bindPopup(` + <div class="flex flex-row justify-center justify-items-center"> + <img onclick="location.href='${link}';" class="cursor-pointer object-cover rounded-[10%] select-none w-12 h-12 mr-1" src="${THUMB_BASE.replace('%s', thumbnail)}"> + <span><b><a href="${link}">${title}</a></b><br><a href="https://www.openstreetmap.org/#map=13/${x}/${y}">${x}, ${y}</a></span> + </div>`); - if (relativeErrorMeters != 0) { - var circle = L.circle([x, y], { - color: color, - fillColor: color, - fillOpacity: 0.15, - radius: relativeErrorMeters - }).addTo(map); - } + markers.addLayer(marker); + } - var marker = L.marker([x, y], { - icon: createCustomIcon(color), - title: title, - }).bindPopup(` - <div class="flex flex-row justify-center justify-items-center"> - <img onclick="location.href='${link}';" class="cursor-pointer object-cover rounded-[10%] select-none w-12 h-12 mr-1" src="${THUMB_BASE.replace('%s', thumbnail)}"> - <span><b><a href="${link}">${title}</a></b><br><a href="https://www.openstreetmap.org/#map=13/${x}/${y}">${x}, ${y}</a></span> - </div>`); + document.addEventListener('DOMContentLoaded', () => { + initLocationMap(); + {{- range .MapMarkers }} + mapAddMarker({{ .Lat }}, {{ .Long }}, {{ .AccuracyMeters }}, {{ .Title }}, {{ .PageLink }}, {{ .Thumbnail }}, {{ fdiv .Index (len $.MapMarkers) }}); + {{- end }} + }); - markers.addLayer(marker); - } - - document.addEventListener('DOMContentLoaded', () => { - initLocationMap(); - {{- range .MapMarkers }} - mapAddMarker({{ .Lat }}, {{ .Long }}, {{ .AccuracyMeters }}, {{ .Title }}, {{ .PageLink }}, {{ .Thumbnail }}, {{ fdiv .Index (len $.MapMarkers) }}); - {{- end }} - }); + window.addEventListener('resize', function() { + setTimeout(() => { + map.invalidateSize(); + }, 100); + }); + </script> - window.addEventListener('resize', function() { - setTimeout(() => { - map.invalidateSize(); - }, 100); - }); - </script> - </body> +</body> </html> diff --git a/views/pages/robots.txt b/views/pages/robots.txt index aaef3f4..1e5bebb 100644 --- a/views/pages/robots.txt +++ b/views/pages/robots.txt @@ -1,7 +1,5 @@ User-agent: * Allow: / -Disallow: /api/v1/email/ -Disallow: /ru/user/ -Disallow: /en/user/ +Disallow: /api/ Sitemap: {{ .CanonicalEndpoint }}/sitemap.xml
\ No newline at end of file diff --git a/views/pages/unsubscribe-page.html b/views/pages/unsubscribe-page.html index c9de473..bb5d64b 100644 --- a/views/pages/unsubscribe-page.html +++ b/views/pages/unsubscribe-page.html @@ -1,65 +1,34 @@ -<!doctype html> +<!DOCTYPE html> <html lang="en"> - <head> - <meta charset="UTF-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <meta name="robots" content="noindex,nofollow" /> - <title>{{ .L.UnsubscribePage.Header }} // SAYA.UZ</title> - <style> - .bg-paper { - background-image: url(data:image/webp;base64,UklGRi4BAABXRUJQVlA4ICIBAABQCQCdASqAAIAAPm02lUmkIqIkIImIgA2JaW3rMFmGDAOKLAoJtP591ByFR74Tun2DP+Lygpfsz5FH7+cKgxi2Islfd03i5AJdXUfU0SDc8SXYAAD++hlzeYAJWQEF8ZdkJdlcA+5btZg0FMGaso8SHoBph0qAOQysElJ5C5SJFNoKIV1J550UyL7Y9Gh0/c/uzLnBmr8lvz7k1WeA81H4S5ViU/wBqa7aM1m8D9rDtrk8cSTqx+xWF6MS8UfZGSsks6CT/igtgv6syFN9uNA3LjnjiCJKYOvFcMkX+jB2578GWVo3nyLaM1QNPYCxn3TiUbnf7U0MdD2NsBpg1+Jd1wu6yW/U+goN+IQS6Nt4RMWltVcZHlxrneKJi0SobO0AAA==); - background-size: 128px; - background-repeat: repeat; - background-blend-mode: multiply; - } - </style> - </head> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="robots" content="noindex,nofollow"> + <title>{{ .L.UnsubscribePage.Header }} // SAYA TODAY</title> +<style> + .bg-paper { + background-image: url(data:image/webp;base64,UklGRi4BAABXRUJQVlA4ICIBAABQCQCdASqAAIAAPm02lUmkIqIkIImIgA2JaW3rMFmGDAOKLAoJtP591ByFR74Tun2DP+Lygpfsz5FH7+cKgxi2Islfd03i5AJdXUfU0SDc8SXYAAD++hlzeYAJWQEF8ZdkJdlcA+5btZg0FMGaso8SHoBph0qAOQysElJ5C5SJFNoKIV1J550UyL7Y9Gh0/c/uzLnBmr8lvz7k1WeA81H4S5ViU/wBqa7aM1m8D9rDtrk8cSTqx+xWF6MS8UfZGSsks6CT/igtgv6syFN9uNA3LjnjiCJKYOvFcMkX+jB2578GWVo3nyLaM1QNPYCxn3TiUbnf7U0MdD2NsBpg1+Jd1wu6yW/U+goN+IQS6Nt4RMWltVcZHlxrneKJi0SobO0AAA==); + background-size: 128px; + background-repeat: repeat; + background-blend-mode: multiply; + } +</style> +</head> - <body - style=" - width: 100dvw; - height: 100dvh; - display: flex; - flex-direction: column; - position: fixed; - " - > - <div - class="bg-paper" - style="flex: 3 0 0; background-color: rgba({{ .StatusColor }}, 0.1)" - ></div> +<body style="width: 100dvw; height: 100dvh; display: flex; flex-direction: column; position: fixed"> - <div class="bg-paper" style="flex: 1 0 0; align-content: center"> - <p - style=" - position: relative; - width: 100%; - margin-inline: auto; - text-align: center; - font-size: 2rem; - " - > - {{ .StatusEmoji }} - </p> - </div> + <div class="bg-paper" style='flex: 3 0 0; background-color: rgba({{ .StatusColor }}, 0.1)'></div> - <div class="bg-paper" style="flex: 1 0 0; align-content: center"> - <p - style=" - position: relative; - width: 100%; - margin-inline: auto; - text-align: center; - font-size: 1.5rem; - " - > - {{ .StatusText }} - </p> - </div> + <div class="bg-paper" style="flex: 1 0 0; align-content: center;"> + <p style="position: relative; width: 100%; margin-inline: auto; text-align: center; font-size: 2rem;">{{ .StatusEmoji }}</p> + </div> + + <div class="bg-paper" style="flex: 1 0 0; align-content: center;"> + <p style="position: relative; width: 100%; margin-inline: auto; text-align: center; font-size: 1.5rem;">{{ .StatusText }}</p> + </div> + + <div class="bg-paper" style='flex: 3 0 0; background-color: rgba({{ .StatusColor }}, 0.2)'></div> + +</body> - <div - class="bg-paper" - style="flex: 3 0 0; background-color: rgba({{ .StatusColor }}, 0.2)" - ></div> - </body> </html> diff --git a/views/partials/general-page-footer.html b/views/partials/general-page-footer.html index 3f5f9ee..3001d5d 100644 --- a/views/partials/general-page-footer.html +++ b/views/partials/general-page-footer.html @@ -1,23 +1,7 @@ {{ block "footer" . }}{{ end }} -<p - class="grow content-center text-base/[1] font-gentium font-thin italic text-right text-secondary mr-1" -> - All the photos on <a href="https://saya.uz/">saya.uz</a> are licensed under - <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a> - by <a href="https://t.me/EarlInisMona">Saya Andy</a> © {{ .PublishedYear }} +<p class="grow content-center text-base/[1] font-gentium font-thin italic text-right text-secondary mr-1"> + All the photos on <a href="https://saya.today/">saya.today</a> are licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a> by <a href="https://t.me/EarlInisMona">Saya Andy</a> © {{ .PublishedYear }} </p> -<img - class="max-w-4 max-h-4 self-center" - src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" - alt="CC" -/> -<img - class="max-w-4 max-h-4 self-center" - src="https://mirrors.creativecommons.org/presskit/icons/by.svg" - alt="BY" -/> -<img - class="max-w-4 max-h-4 self-center" - src="https://mirrors.creativecommons.org/presskit/icons/sa.svg" - alt="SA" -/> +<img class="max-w-4 max-h-4 self-center" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" alt="CC"> +<img class="max-w-4 max-h-4 self-center" src="https://mirrors.creativecommons.org/presskit/icons/by.svg" alt="BY"> +<img class="max-w-4 max-h-4 self-center" src="https://mirrors.creativecommons.org/presskit/icons/sa.svg" alt="SA">
\ No newline at end of file diff --git a/views/partials/general-page-header.html b/views/partials/general-page-header.html index 995ebf3..ad11ffb 100644 --- a/views/partials/general-page-header.html +++ b/views/partials/general-page-header.html @@ -1,9 +1,5 @@ -<title>{{ .Title }} // SAYA.UZ</title> +<title>{{ .Title }} // SAYA TODAY</title> <div class="flex flex-row mb-2"> {{ block "header" . }}{{ end }} - <p - class="text-4xl font-m-plus font-bold italic text-shadow-[0_2px_2px_var(--color-main-soft)] text-left my-auto" - > - {{ .Title }} - </p> -</div> + <p class="text-4xl font-m-plus font-bold italic text-shadow-[0_2px_2px_var(--color-main-soft)] text-left my-auto">{{ .Title }}</p> +</div>
\ No newline at end of file |