• Как правильно задеплоить next js и wordpress с ci/cd из гитхаба?

    У меня был опыт работы Next + WP в продакшне. Самый удобный и реиспользуемый подход, который получился опытным путём - это упаковка всего вышеперечисленного в докер контейнеры. Таким образом мы упрощаем деплой примерно в 100 раз (несколько десятков команд против одной docker compose up -d).

    1. Нужно развернуть WP + MySQL+ PHPMyAdmin + MailHog (если есть необходимость тестировать кучу писем локально)

    docker-compose.yml
    version: '3.1'
    
    services:
    
      wp:
        container_name: ${NAME}-wordpress
        image: wordpress:latest # https://hub.docker.com/_/wordpress/
        ports:
          - ${IP}:${PORT}:80 # change ip if required
        volumes:
          - ./config/php.conf.ini:/usr/local/etc/php/conf.d/conf.ini
    #      - ./wp-app:/var/www/html  Full wordpress project
    #      - ./plugins:/var/www/html/wp-content/plugins
          #- ./plugin-name/trunk/:/var/www/html/wp-content/plugins/plugin-name # Plugin development
          - ./${REPO_THEME_NAME}:/var/www/html/wp-content/themes/${REPO_THEME_NAME} # Theme development
        environment:
          WORDPRESS_DB_HOST: db
          WORDPRESS_DB_NAME: "${DB_NAME}"
          WORDPRESS_DB_USER: root
          WORDPRESS_DB_PASSWORD: "${DB_ROOT_PASSWORD}"
        depends_on:
          - db
        links:
          - db
    
      pma:
        container_name: ${NAME}-phpmyadmin
        image: phpmyadmin/phpmyadmin
        environment:
          # https://docs.phpmyadmin.net/en/latest/setup.html#docker-environment-variables
          PMA_HOST: db
          PMA_PORT: 3306
          MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD}"
          UPLOAD_LIMIT: 50M
        ports:
          - ${IP}:8080:80
        links:
          - db:db
    
      db:
        container_name: ${NAME}-mysql
        image: mysql:latest # https://hub.docker.com/_/mysql/ - or mariadb https://hub.docker.com/_/mariadb
        ports:
          - ${IP}:3306:3306 # change ip if required
        volumes:
          - ./wp-data:/docker-entrypoint-initdb.d
          - db_data:/var/lib/mysql
        environment:
          MYSQL_DATABASE: "${DB_NAME}"
          MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD}"
    
      mailhog:
        container_name: ${NAME}-mailhog
        image: mailhog/mailhog
        ports:
          - "8025:8025"
          - "1025:1025"
    volumes:
      db_data:
    
    networks:
      wordpress:
        name: wp-wordpress
        driver: bridge


    .env

    REPO_THEME_NAME=nov-dom
    NAME=NOVDOM
    IP=127.0.0.1
    PORT=80
    DB_ROOT_PASSWORD=password
    DB_NAME=wordpress


    Итак, мы получили работающих сервер с Wordpress. Выше описанный пример мы используем для локальной разработки тем, но его легко адаптировать под любые нужны.
    Важно, чтобы в процессе локальной разработки у тебя не был смонтирован весь Wordpress, в таком случае комп будет обрабатывать перенос всего ядра при каждом сохранении файлов внутрь контейнера. Оставляй только файлы темы, для такой разработки хватит и калькулятора.

    При основном деплое, если будет желание прокинуть какие-то особенные файлы, это делается легко через docker exec и docker cp из локальной машины внутрь виртуалки докера.

    docker-compse файл для Next простой настолько насколько это возможно.

    version: '3'
    services:
      nextjs:
        build: ./
        ports:
        - "8080:3000"


    После остаётся проксировать трафик с портов на определенные роуты.

    Самый простой вариант - накатить на 81 порт машины NGINX Proxy Manager
    Через него в визуальном интерфейсе можно сделать примерно всё что угодно с трафиком без гемороя с конфигами. (Такое простое проксирование уж подавно)

    Docker compose для NPM можно найти на оф.сайте

    version: '3.8'
    services:
      app:
        image: 'jc21/nginx-proxy-manager:latest'
        restart: unless-stopped
        ports:
          # These ports are in format <host-port>:<container-port>
          - '80:80' # Public HTTP Port
          - '443:443' # Public HTTPS Port
          - '81:81' # Admin Web Port
          # Add any other Stream port you want to expose
          # - '21:21' # FTP
        environment:
          # Mysql/Maria connection parameters:
          DB_MYSQL_HOST: "db"
          DB_MYSQL_PORT: $DB_MYSQL_PORT
          DB_MYSQL_USER: $DB_MYSQL_USER
          DB_MYSQL_PASSWORD: $DB_MYSQL_PASSWORD
          DB_MYSQL_NAME: $DB_MYSQL_NAME
          # Uncomment this if IPv6 is not enabled on your host
          DISABLE_IPV6: 'true'
        volumes:
          - ./data:/data
          - ./letsencrypt:/etc/letsencrypt
        depends_on:
          - db
    
      db:
        image: 'jc21/mariadb-aria:latest'
        restart: unless-stopped
        environment:
          MYSQL_ROOT_PASSWORD: $DB_MYSQL_ROOT_PASSWORD
          MYSQL_DATABASE: $DB_MYSQL_NAME
          MYSQL_USER: $DB_MYSQL_USER
          MYSQL_PASSWORD: $DB_MYSQL_PASSWORD
        volumes:
          - ./mysql:/var/lib/mysql

    Конфиги из разных проектов, надо поднять базы на разных портах.

    .env
    DB_MYSQL_PORT=3306
    DB_MYSQL_USER=example
    DB_MYSQL_ROOT_PASSWORD=example
    DB_MYSQL_PASSWORD=example
    DB_MYSQL_NAME=example


    ***Всё, что описывается выше, это личный опыт, никого не призываю это брать информацию и считать ее единственной верной. Есть решения лучше и производительнее, но для реализации это проекта, требующие более глубинных знаний

    ***** Думаю с деплоем контейнеров, у тебя проблем не возникнет

    Насчет того, где разворачивать. Пользуюсь Бегетом около 4-х лет, и больше всего меня подкупает, что у них круглосуточная поддержка. Но ценник может кусаться. Качество оборудования, отказоустойчивость, API для получения статистики, есть всё.

    Как альтернативу можешь использовать FirstByte, дешевый и сердитый
    Ответ написан
    3 комментария
  • Как работать с RabbitMQ через докер?

    glaphire
    @glaphire
    PHP developer
    Если реббит в отдельном контейнере, то нужно чтобы лара и реббит были в одной подсети докера,у Вас в docker-compose написаны только настройки контейнера без подсети)
    https://forums.docker.com/t/internal-network-betwe...
    В комментах есть примеры, от чего отталкиваться при поиске
    Ответ написан
    Комментировать
  • Где лучше размещать изображения товаров интернет-магазина?

    Stalker_RED
    @Stalker_RED
    Проще и дешевле хранить локально. Но чем больше посетителей, чем шире их география, тем выгоднее перебросить картинки в CDN.
    Неплохой вариант сделать сразу поддомен для статики, и пусть он изначально даже ведет на тот-же сервер где и основное приложение, но как только понадобится масштабирование вы сможете быстро перебросить его куда угодно.
    Единственный накладной расход - сразу придется делать простенькое API для аплоада картинок, а не хардкодить move_uploaded_file()
    Ответ написан
    Комментировать
  • Можно ли как-то указать для элемента дробную ширину колонки?

    Mike_Ro
    @Mike_Ro
    Python, JS, WordPress, SEO, Bots, Adversting
    7 колонок, где первую колонку занимает высокая колонка, а остальные 6 колонок легко делятся и на 2 и на 3
    Ответ написан
    Комментировать
  • Как подружиться c SMTP от Яндекса?

    Prosto
    @Prosto
    Сгенерировал одноразовый пароль - не помогло.
    Потом включил в настройках яндекса
    Разрешить доступ к почтовому ящику с помощью почтовых клиентов
    С сервера imap.yandex.ru по протоколу IMAP

    Хотя казалось бы при чем тут smtp
    И всё заработало
    Ответ написан
    7 комментариев
  • Как вызвать данные в функцию?

    @Wispik
    Вот схематический пример, как сделать:
    <div v-if="game.Winner=== game.Player1Name">тут кубок</div>

    Ну и вместо этого:
    const $player1 = document.querySelector('player1')
    во vue принято использовать ref
    Ответ написан
    1 комментарий
  • К переменной в js не прибавляется число. Что не так?

    yarkov
    @yarkov Куратор тега JavaScript
    Помог ответ? Отметь решением.
    В чем проблема?

    В отсутствии базовых знаний языка и непонимании написанного вами кода. Пройдите глазами по коду, построчно, вслух проговаривая что происходит на каждой строке. Если не поможет - отпишитесь)) Подскажу в чём именно проблема.
    Ответ написан
    Комментировать
  • Как автоматизировать сбор замеров (DevTools) статистики открытия веб-страниц сайта в БД?

    dimonchik2013
    @dimonchik2013
    non progredi est regredi
    открой для себя APM - Newrelic и все такое подобное,
    + автотесты фронтэнда - на ошибки,
    + фичи от Cloudflare да и того же гугла по средней загрузке страниц

    а если в СЕО играетесь - то Селениум
    Ответ написан
    2 комментария
  • Требования к самописной CRM?

    Jeer
    @Jeer
    уверенный пользователь
    Аж жуть берет от таких заданий ) Покупка готового инструмента обычно дешевле, чем разработка с нуля. Особенно если разработка осуществляется одним человеком. Особенно если опыта мало. Особенно, если на поддержке будет тот же человек, что и на разработке ) безумие :) проект полетит в помойку, но у вас будет строчка в резюме по созданию проекта, можете обкатывать любые технологии за счет глупого работодателя.

    По делу без брюзжания:
    Писать под винду моветон - лицензии дорогие
    Вин формс зачем? - делайте веб апи с фронтом на вью/реакте/ангуляре. Нужна кроссплатформенность, чтобы хоть с телефона можно было зайти и нажать нужную кнопку в системе.
    Апи нужно, потому что будет много интеграций с другими системами, загрузка/выгрузка в 1с, не дай бог будете телефонию подключать
    Внутренняя сеть решается с помощью впн сервера.
    Помимо функциональных требований существуют еще технические, должен быть мониторинг, вы должны всегда знать сколько ресурсов потребляет ваш сервер, сколько данных занимает на дисках, логи и трейсы - гуглится по слову Observability, обычно не закладывается в смету, но к этим вопросам приходят рано или поздно
    Что еще, ну, по функционалу тут проще, открываете презентации в популярных CRMках, смотрите, что они могут, выписываете списком и идете к заказчику, чтобы он указал галочками, что будете делать, что не будете
    потом прикидываете по трудозатратам и озвучиваете сроки из которых можно понять примерную стоимость проекта. Часто одно маленькое предложение, типа того же "выгрузить данные в 1с" грозит несколькими десяткми часов работы, потому что другой отдел, с ними нужно договориться и сделать интеграцию. Или "должен быть отчет такой-то", а там как начнешь разбираться, еще 5 раз посовещаться сначала надо ))
    Ответ написан
    Комментировать
  • Как испраить ошибку: SQLSTATE: Duplicate alias: имя таблицы указано больше одного раза?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Пожалуйста, называйте связи так, как называется модель, которую вы хотите получить.

    items() ? чего? вы хотите получить CartProduct. назовите cartProducts()

    очень редко, но бывает, когда CartProduct можно получить двумя способами. Тогда cartProductsByModel1(), cartProductsByModel2()

    исключение - абстрактные паттерны, деревья. там можно назвать parent/children/treeParent/treeChild

    <?php
    
    Class Cart {
        // ...
    
        // вот здесь написано, что CartProduct может быть одновременно в двух и более корзинах. не могу представить себе логику, где участник заказывает на сайте товар, а потом этот же товар (который ты заморозил только что) кто-то еще заказывает
        public function items(): BelongsToMany
        {
            return $this->belongsToMany(CartProduct::class, 'cart_products');
            
            // Cart.hasMany(CartProduct) + CartProduct.belongsTo(Cart) + CartProduct.belongsTo(Product)
            // и второе
            // https://github.com/illuminate/database/blob/master/Eloquent/Concerns/HasRelationships.php#L477
            // первым параметром "тип", вторым - "через что". Вы указываете, что "связь много ко многим" должна вернуть "связь много ко многим"
            // при связи Много-Ко-Многим надо Cart.belongsToMany(Product::class, 'cart_products')
        }
    }


    <?php
    
    class CartProduct {
        // ...
    
        // здесь написано, что CartProduct старше чем Product. Это выразится в том, что вы сможете "привязать" CartProduct только из самого Product, причем один раз. Это дважды неверно.
        public function product(): HasOne
        {
            return $this->hasOne(ProductVariant::class, 'id');
    
            // Во первых каждый продукт с его количеством может быть куплен много раз, а вариант всего лишь показывает на его подвид
            // Во вторых модель CartProduct создается позже чем Product, а значит писать $product.addChild($cartProduct) это снова дичь
            // Product.hasMany(ProductVariant::class) + CartProduct.belongsTo(ProductVariant::class) + $cartProduct.associate($productVariant);
        }
    
        // то же самое. CartProduct старше чем Cart, хотя он в ней лежит. BelongsTo задает старшинство и позволяет добавлять родителя в потомок, а hasOne указывает связь, которая нужна только для select, но не годится для insert
        // Потому что hasOne() - это возможность добавить потомка, причем в Eloquent нету метода addChild() его ещё и самому писать надо типа CartProduct.cart().insert($array) и это тут же нарушает идею отложенной записи, т.к. выполняет запрос немедленно и требует транзакции прямо в модели, тогда как транзакция относится к задаче, а не к модели
        public function cart(): HasOne
        {
            return $this->hasOne(Cart::class);
        }
    }
    Ответ написан
    Комментировать
  • Как испраить ошибку: SQLSTATE: Duplicate alias: имя таблицы указано больше одного раза?

    iMedved2009
    @iMedved2009
    Не люблю людей
    Это лажа
    public function items(): BelongsToMany
        {
            return $this->belongsToMany(CartProduct::class, 'cart_products');
        }

    Вы классу CartProduct прописываете отношение к самому CartProduct

    Наверное должно быть так?
    public function items(): BelongsToMany
        {
            return $this->belongsToMany(ProductVariant::class, 'cart_products');
        }
    Ответ написан
    Комментировать
  • Как правильно провести десериализацию xml файла с вложенными объектами при помощи Symfony Serializer?

    BoShurik
    @BoShurik Куратор тега Symfony
    Symfony developer
    В общем случае надо добавить аннотацию @var GroupDto[] чтоб сериалайзер понимал, что там типизированный массив.
    Плюс придется написать свой Normalizer, т.к. при нормализации родительской группы вы ручками "игнорируете" ноду "Группа"
    Ответ написан
    1 комментарий
  • Как забиндить две реализации интерфейса для гостя и авторизованного пользователя соответственно?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега Laravel
    Вместо провайдера можно использовать глобальный middleware. Не очень красиво, но технически - это самый прямой способ добиться нужного результата.
    Ответ написан
    1 комментарий
  • Как сделать так, чтобы можно было находить слово в тексте?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Заменять регулярным выражением вхождения поисковой подстроки на её же, обёрнутую в тег <span> который стилями сделает текст внутри него красным:

    Но, поскольку строка поиска становится регулярным выражением, можно в поиск ввести, например, [о-т] для поиска всех букв из диапазона: о, п, р, с, т.
    А простая точка выберет вообще весь текст. Поэтому хорошо бы в поисковой строке сначала экранировать все спец-символы из арсенала регулярных выражений.
    Ответ написан
    Комментировать
  • Как использовать тернарный оператор на групе кода?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Тернарный оператор — чтобы возвращать значение.
    Это не модная замена if .. else

    В вашем примере лучше записать традиционно:
    if (something) {
      console.log(0);
      console.log('something else');
    } else {
      console.log(false);
    }
    Ответ написан
    Комментировать
  • Как перезагрузить страницу после деплоя?

    thewind
    @thewind
    php программист, front / backend developer
    Если есть бэк, то сделать метод апи с версией. Дергать его раз в N секунд. И сохранять. Если поменялось - запоминаем новый и перезагружаем страницу.
    Ответ написан
    3 комментария
  • Как лучше подключать scss bem блоки?

    delphinpro
    @delphinpro Куратор тега Sass
    frontend developer
    Не сваливайте все блоки в одну папку, а делите по уровням абстракции.
    Кнопка – примитив
    Карточка – компонент более высокого порядка, может содержать в себе в том числе кнопки.

    @use primitives/**
    @use components/**
    Ответ написан
    Комментировать
  • С чего начать в 30 лет?

    gbg
    @gbg
    Любые ответы на любые вопросы
    Фундаментальная проблема - в игрострой стоит очередь фанатов с горящими глазами. Людей, которые пишут игры, играют в игры, моддят игры, хакают игры..., начиная с возраста, когда они смогли дотянуться до клавиатуры (как ваш покорный слуга). При этом, у них столько мотивации, что они могут сами поставить себе задачу, сами нагуглить все, что нужно (Спасибо дяде Немнюгину за его учебник. Это все что нужно знать о DOS, чтобы закодить dOOm) и сами все напишут и нарисуют (работая над этим сутками. Не потому что над душей стоит босс с воплями о лишении премии, а потому что им это интересно).

    И вот теперь, Андрей, который уже устал хочет встать в очередь высоко мотивированных конкурентов и попросить себе оффер. Каковы его шансы на успех?

    В играх есть много специализаций программирования - хотите графику, придется учить одно (причем на 2d и 3d это одно будет разное), хотите игровую логику и скриптинг - это номер два, хотите системную часть и бэкенд - это будет третье.

    Для универсального развития, нужно начинать с простых фиговин вроде тетриса, арканоида, бильярда и прочего (в которых однако есть все столпы игростроя - графика + логика + системный движок, можно и добавить сеть, если подумать) и потом наращивать сложность - делать платформер или RTS.
    Ответ написан
    4 комментария
  • Как сделать отмеченную форму checkbox устойчивой к обновлению страницы в браузере на Vue.js?

    0xD34F
    @0xD34F Куратор тега Vue.js
    Зачем отдельный массив checked? Пусть notes вместо массива строк будет массивом объектов, состоящих из двух свойств - text и checked. Наблюдатель c deep: true, сохраняющий данные в localStorage уже есть, так что никаких дополнительных действий предпринимать не придётся.

    UPD. Как это может выглядеть:

    <div id="app">
      <div>
        <input v-model="newTaskText" @keypress.enter="addTask">
        <button @click="addTask">add</button>
      </div>
      <hr>
      <ol v-if="tasks.length">
        <li v-for="(n, i) in tasks">
          <label :class="{ 'task-checked': n.checked }">
            <input type="checkbox" v-model="n.checked">
            {{ n.text }}
          </label>
          <button @click="delTask(i)">del</button>
        </li>
      </ol>
      <strong>Total: {{ tasks.length || 'no tasks fucking exist' }}</strong>
    </div>

    .task-checked {
      text-decoration: line-through;
    }

    Vue.createApp({
      data: () => ({
        newTaskText: '',
        tasks: JSON.parse(localStorage.getItem('tasks')) ?? [],
      }),
      watch: {
        tasks: {
          deep: true,
          handler: val => localStorage.setItem('tasks', JSON.stringify(val)),
        },
      },
      methods: {
        addTask() {
          const text = this.newTaskText.trim();
          if (text) {
            this.tasks.push({
              text,
              checked: false,
            });
    
            this.newTaskText = '';
          } else {
            alert('fuck off');
          }
        },
        delTask(index) {
          if (confirm('really?')) {
            this.tasks.splice(index, 1);
          }
        },
      },
    }).mount('#app');
    Ответ написан
    8 комментариев