diff --git a/app/controllers/api/v1/webhooks_controller.rb b/app/controllers/api/v1/webhooks_controller.rb new file mode 100644 index 00000000..36e6a6d1 --- /dev/null +++ b/app/controllers/api/v1/webhooks_controller.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +module Api + module V1 + # Recibe webhooks y lanza un PullJob + class WebhooksController < BaseController + # responde con forbidden si falla la validación del token + rescue_from ActiveRecord::RecordNotFound, with: :platforms_answer + + # Trae los cambios a partir de un post de Webhooks: + # (Gitlab, Github, Gitea, etc) + # + # @return [nil] + def pull + message = I18n.with_locale(site.default_locale) do + I18n.t('webhooks.pull.message') + end + + GitPullJob.perform_later(site, usuarie, message) + head :ok + end + + private + + # encuentra el sitio a partir de la url + def site + @site ||= Site.find_by_name!(params[:site_id]) + end + + # valida el token que envía la plataforma del webhook + # + # @return [String] + def token + @token ||= + begin + # Gitlab + if request.headers['X-Gitlab-Token'].present? + request.headers['X-Gitlab-Token'] + # Github + elsif request.headers['X-Hub-Signature-256'].present? + token_from_signature(request.headers['X-Hub-Signature-256'], 'sha256=') + # Gitea + elsif request.headers['X-Gitea-Signature'].present? + token_from_signature(request.headers['X-Gitea-Signature']) + else + raise ActiveRecord::RecordNotFound, 'proveedor no soportado' + end + end + end + + # valida token a partir de firma de webhook + # + # @return [String, Boolean] + def token_from_signature(signature, prepend = '') + payload = request.body.read + site.roles.where(temporal: false, rol: 'usuarie').pluck(:token).find do |token| + new_signature = prepend + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), token, payload) + ActiveSupport::SecurityUtils.secure_compare(new_signature, signature.to_s) + end.tap do |t| + raise ActiveRecord::RecordNotFound, 'token no encontrado' if t.nil? + end + end + + # encuentra le usuarie + def usuarie + @usuarie ||= site.roles.find_by!(temporal: false, rol: 'usuarie', token: token).usuarie + end + + # respuesta de error a plataformas + def platforms_answer(exception) + ExceptionNotifier.notify_exception(exception, data: { headers: request.headers.to_h } + + head :forbidden + end + end + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ee153394..2746ab10 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -59,7 +59,11 @@ class ApplicationController < ActionController::Base # # @return [String,Symbol] def current_locale - session[:locale] = params[:change_locale_to] if params[:change_locale_to].present? + locale = params[:change_locale_to] + + if locale.present? && I18n.locale_available?(locale) + session[:locale] = params[:change_locale_to] + end session[:locale] || current_usuarie&.lang || I18n.locale end diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 078e16f8..9d8c42a4 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -24,6 +24,7 @@ class PostsController < ApplicationController # Todos los artículos de este sitio para el idioma actual @posts = site.indexed_posts.where(locale: locale) + @posts = @posts.page(filter_params.delete(:page)) if site.pagination # De este tipo @posts = @posts.where(layout: filter_params[:layout]) if filter_params[:layout] # Que estén dentro de la categoría @@ -156,7 +157,7 @@ class PostsController < ApplicationController # # @return [Hash] def filter_params - @filter_params ||= params.permit(:q, :category, :layout).to_hash.select do |_, v| + @filter_params ||= params.permit(:q, :category, :layout, :page).to_hash.select do |_, v| v.present? end.transform_keys(&:to_sym) end diff --git a/app/controllers/sites_controller.rb b/app/controllers/sites_controller.rb index 63865e44..17287eb0 100644 --- a/app/controllers/sites_controller.rb +++ b/app/controllers/sites_controller.rb @@ -57,6 +57,7 @@ class SitesController < ApplicationController usuarie: current_usuarie) if service.update.valid? + flash[:notice] = I18n.t('sites.update.post') redirect_to site_posts_path(site, locale: site.default_locale) else render 'edit' diff --git a/app/jobs/git_pull_job.rb b/app/jobs/git_pull_job.rb new file mode 100644 index 00000000..dc4a285c --- /dev/null +++ b/app/jobs/git_pull_job.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# Permite traer los cambios desde webhooks + +class GitPullJob < ApplicationJob + # @param :site [Site] + # @param :usuarie [Usuarie] + # @param :message [String] + # @return [nil] + def perform(site, usuarie, message) + site.repository.merge(usuarie, message) if site.repository.fetch&.positive? + end +end \ No newline at end of file diff --git a/app/models/deploy_distributed_press.rb b/app/models/deploy_distributed_press.rb index 889d8e34..2c892b55 100644 --- a/app/models/deploy_distributed_press.rb +++ b/app/models/deploy_distributed_press.rb @@ -52,7 +52,12 @@ class DeployDistributedPress < Deploy end end - status = c.publish(publishing_site, deploy_local.destination) + begin + status = c.publish(publishing_site, deploy_local.destination) + rescue DistributedPress::V1::Error => e + ExceptionNotifier.notify_exception(e, data: { site: site.name }) + status = false + end if status self.remote_info[:distributed_press] = c.show(publishing_site).to_h diff --git a/app/models/deploy_local.rb b/app/models/deploy_local.rb index 0bb3958f..bf1b7680 100644 --- a/app/models/deploy_local.rb +++ b/app/models/deploy_local.rb @@ -130,7 +130,12 @@ class DeployLocal < Deploy end def bundle(output: false) - run %(bundle install --deployment --no-cache --path="#{gems_dir}" --clean --without test development), output: output + run %(bundle config set --local clean 'true'), output: output + run %(bundle config set --local deployment 'true'), output: output + run %(bundle config set --local path '#{gems_dir}'), output: output + run %(bundle config set --local without 'test development'), output: output + run %(bundle config set --local cache_all 'false'), output: output + run %(bundle install), output: output end def jekyll_build(output: false) diff --git a/app/models/metadata_related_posts.rb b/app/models/metadata_related_posts.rb index a9aaaa21..6d52096e 100644 --- a/app/models/metadata_related_posts.rb +++ b/app/models/metadata_related_posts.rb @@ -8,9 +8,9 @@ class MetadataRelatedPosts < MetadataArray # # @return [Hash] def values - @values ||= posts.pluck(:title, :layout, :post_id).to_h do |row| + @values ||= posts.pluck(:title, :created_at, :layout, :post_id).to_h do |row| row.tap do |value| - value[0] = "#{value[0]} (#{site.layouts[value.delete_at(1)].humanized_name})" + value[0] = "#{value[0]} #{value.delete_at(1).strftime('%F')} (#{site.layouts[value.delete_at(1)].humanized_name})" end end end diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index 6e13aecd..72bec64b 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -210,7 +210,7 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, def allowed_attributes @allowed_attributes ||= %w[style href src alt controls data-align data-multimedia data-multimedia-inner id - name].freeze + name start].freeze end def allowed_tags diff --git a/app/models/rol.rb b/app/models/rol.rb index fcd07037..37332400 100644 --- a/app/models/rol.rb +++ b/app/models/rol.rb @@ -14,6 +14,8 @@ class Rol < ApplicationRecord validates_inclusion_of :rol, in: ROLES + before_save :add_token_if_missing! + def invitade? rol == INVITADE end @@ -25,4 +27,11 @@ class Rol < ApplicationRecord def self.role?(rol) ROLES.include? rol end + + private + + # Asegurarse que tenga un token + def add_token_if_missing! + self.token ||= SecureRandom.hex(64) + end end diff --git a/app/models/site/index.rb b/app/models/site/index.rb index 2f2d1dc9..2cb950cb 100644 --- a/app/models/site/index.rb +++ b/app/models/site/index.rb @@ -1,17 +1,19 @@ # frozen_string_literal: true -# Indexa todos los artículos de un sitio -# -# TODO: Hacer opcional class Site + # Indexa todos los artículos de un sitio + # + # TODO: Hacer opcional module Index extend ActiveSupport::Concern included do - # TODO: Debería ser un Job? - after_create :index_posts! has_many :indexed_posts, dependent: :destroy + MODIFIED_STATUSES = %i[added modified].freeze + DELETED_STATUSES = %i[deleted].freeze + LOCALE_FROM_PATH = /\A_/.freeze + def index_posts! Site.transaction do jekyll.read @@ -24,6 +26,105 @@ class Site update(last_indexed_commit: repository.head_commit.oid) end end + + # Encuentra los artículos modificados entre dos commits y los + # reindexa. + def reindex_changes! + return unless reindexable? + + Site.transaction do + remove_deleted_posts! + reindex_modified_posts! + + update(last_indexed_commit: repository.head_commit.oid) + end + end + + # No hacer nada si el repositorio no cambió o no hubo cambios + # necesarios + def reindexable? + return false if last_indexed_commit.blank? + return false if last_indexed_commit == repository.head_commit.oid + + !indexable_posts.empty? + end + + private + + # Trae el último commit indexado desde el repositorio + # + # @return [Rugged::Commit] + def indexed_commit + @indexed_commit ||= repository.rugged.lookup(last_indexed_commit) + end + + # Calcula la diferencia entre el último commit indexado y el + # actual + # + # XXX: Esto no tiene en cuenta modificaciones en la historia como + # cambio de ramas, reverts y etc, solo asume que se mueve hacia + # adelante en la misma rama o las dos ramas están relacionadas. + # + # @return [Rugged::Diff] + def diff_with_head + @diff_with_head ||= indexed_commit.diff(repository.head_commit) + end + + # Obtiene todos los archivos a reindexar + # + # @return [Array] + def indexable_posts + @indexable_posts ||= + diff_with_head.each_delta.select do |delta| + locales.any? do |locale| + delta.old_file[:path].start_with? "_#{locale}/" + end + end + end + + # Elimina los artículos eliminados o que cambiaron de ubicación + # del índice + def remove_deleted_posts! + indexable_posts.select do |delta| + DELETED_STATUSES.include? delta.status + end.each do |delta| + locale, path = locale_and_path_from(delta.old_file[:path]) + + indexed_posts.destroy_by(locale: locale, path: path).tap do |destroyed_posts| + next unless destroyed_posts.empty? + + Rails.logger.info I18n.t('indexed_posts.deleted', site: name, path: path, records: destroyed_posts.count) + end + end + end + + # Reindexa artículos que cambiaron de ubicación, se agregaron + # o fueron modificados + def reindex_modified_posts! + indexable_posts.select do |delta| + MODIFIED_STATUSES.include? delta.status + end.each do |delta| + locale, path = locale_and_path_from(delta.new_file[:path]) + + posts(lang: locale).find(path).index! + end + end + + # Obtiene el idioma y la ruta del post a partir de la ubicación en + # el disco. + # + # Las rutas vienen en ASCII-9BIT desde Rugged, pero en realidad + # son UTF-8 + # + # @return [Array] + def locale_and_path_from(path) + locale, path = path.force_encoding('utf-8').split(File::SEPARATOR, 2) + + [ + locale.sub(LOCALE_FROM_PATH, ''), + File.basename(path, '.*') + ] + end end end end diff --git a/app/models/site/repository.rb b/app/models/site/repository.rb index 2eafb92e..acbf6553 100644 --- a/app/models/site/repository.rb +++ b/app/models/site/repository.rb @@ -54,7 +54,7 @@ class Site # Incorpora los cambios en el repositorio actual # # @return [Rugged::Commit] - def merge(usuarie) + def merge(usuarie, message = I18n.t('sites.fetch.merge.message')) merge = rugged.merge_commits(head_commit, remote_head_commit) # No hacemos nada si hay conflictos, pero notificarnos @@ -69,12 +69,16 @@ class Site .create(rugged, update_ref: 'HEAD', parents: [head_commit, remote_head_commit], tree: merge.write_tree(rugged), - message: I18n.t('sites.fetch.merge.message'), + message: message, author: author(usuarie), committer: committer) # Forzamos el checkout para mover el HEAD al último commit y # escribir los cambios rugged.checkout 'HEAD', strategy: :force + + git_sh("git", "lfs", "fetch", "origin", default_branch) + # reemplaza los pointers por los archivos correspondientes + git_sh("git", "lfs", "checkout") commit end diff --git a/app/models/usuarie.rb b/app/models/usuarie.rb index 2bc7a1b5..42f20c0b 100644 --- a/app/models/usuarie.rb +++ b/app/models/usuarie.rb @@ -10,6 +10,7 @@ class Usuarie < ApplicationRecord validates_uniqueness_of :email validates_with EmailAddress::ActiveRecordValidator, field: :email + validate :locale_available! before_create :lang_from_locale! before_update :remove_confirmation_invitation_inconsistencies! @@ -78,4 +79,15 @@ class Usuarie < ApplicationRecord self.invitation_accepted_at ||= Time.now.utc end end + + # Muestra un error si el idioma no está disponible al cambiar el + # idioma de la cuenta. + # + # @return [nil] + def locale_available! + return if I18n.locale_available? self.lang + + errors.add(:lang, I18n.t('activerecord.errors.models.usuarie.attributes.lang.not_available')) + nil + end end diff --git a/app/services/site_service.rb b/app/services/site_service.rb index 9d748f8f..34d5d43f 100644 --- a/app/services/site_service.rb +++ b/app/services/site_service.rb @@ -33,6 +33,7 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do add_licencias && add_code_of_conduct && add_privacy_policy && + site.index_posts! && deploy end diff --git a/app/views/layouts/_flash.haml b/app/views/layouts/_flash.haml index 7bd7ec0b..2d65ff78 100644 --- a/app/views/layouts/_flash.haml +++ b/app/views/layouts/_flash.haml @@ -1,4 +1,4 @@ - flash.each do |type, message| - unless type == 'js' = render 'bootstrap/alert' do - = message + = sanitize_markdown message, tags: %w[a strong em] diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index e8c3dea7..ae53aa7a 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -84,7 +84,10 @@ %button.btn{ data: { action: 'reorder#top' } }= t('posts.reorder.top') %button.btn{ data: { action: 'reorder#bottom' } }= t('posts.reorder.bottom') - %div + - if @site.pagination + %div + = link_to_prev_page @posts, t('posts.prev'), class: 'btn' + = link_to_next_page @posts, t('posts.next'), class: 'btn' %tbody - dir = @site.data.dig(params[:locale], 'dir') - size = @posts.size diff --git a/app/views/sites/_form.haml b/app/views/sites/_form.haml index 69997ffa..0dcccbe3 100644 --- a/app/views/sites/_form.haml +++ b/app/views/sites/_form.haml @@ -46,36 +46,37 @@ .invalid-feedback= site.errors.messages[:description].join(', ') %hr/ - .form-group#design_id - %h2= t('.design.title') - %p.lead= t('.help.design') - - if invalid? site, :design_id - = render 'bootstrap/alert' do - = t('activerecord.errors.models.site.attributes.design_id.layout_incompatible.help', - layouts: site.incompatible_layouts.to_sentence) - .row.row-cols-1.row-cols-md-2.designs - -# Demasiado complejo para un f.collection_radio_buttons - - Design.all.order(priority: :desc).each do |design| - .design.col.d-flex.flex-column - .custom-control.custom-radio - = f.radio_button :design_id, design.id, - checked: design.id == site.design_id, - disabled: design.disabled, - required: true, class: 'custom-control-input' - = f.label "design_id_#{design.id}", design.name, - class: 'custom-control-label' - .flex-fill - = sanitize_markdown design.description, - tags: %w[p a strong em] + - unless site.persisted? + .form-group#design_id + %h2= t('.design.title') + %p.lead= t('.help.design') + - if invalid? site, :design_id + = render 'bootstrap/alert' do + = t('activerecord.errors.models.site.attributes.design_id.layout_incompatible.help', + layouts: site.incompatible_layouts.to_sentence) + .row.row-cols-1.row-cols-md-2.designs + -# Demasiado complejo para un f.collection_radio_buttons + - Design.all.order(priority: :desc).each do |design| + .design.col.d-flex.flex-column + .custom-control.custom-radio + = f.radio_button :design_id, design.id, + checked: design.id == site.design_id, + disabled: design.disabled, + required: true, class: 'custom-control-input' + = f.label "design_id_#{design.id}", design.name, + class: 'custom-control-label' + .flex-fill + = sanitize_markdown design.description, + tags: %w[p a strong em] - .btn-group{ role: 'group', 'aria-label': t('.design.actions') } - - if design.url - = link_to t('.design.url'), design.url, - target: '_blank', class: 'btn' - - if design.license - = link_to t('.design.license'), design.license, - target: '_blank', class: 'btn' - %hr/ + .btn-group{ role: 'group', 'aria-label': t('.design.actions') } + - if design.url + = link_to t('.design.url'), design.url, + target: '_blank', class: 'btn' + - if design.license + = link_to t('.design.license'), design.license, + target: '_blank', class: 'btn' + %hr/ .form-group.licenses#license_id %h2= t('.licencia.title') diff --git a/config/environments/production.rb b/config/environments/production.rb index 4cc1cb39..5e089ff9 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -142,7 +142,7 @@ Rails.application.configure do } config.action_mailer.default_options = { from: ENV.fetch('DEFAULT_FROM', "noreply@sutty.nl") } - config.middleware.use ExceptionNotification::Rack, gitlab: {}, ignore_exceptions: (['DeployJob::DeployAlreadyRunningException'] + ExceptionNotifier.ignored_exceptions) + config.middleware.use ExceptionNotification::Rack, gitlab: {}, ignore_exceptions: ['DeployJob::DeployAlreadyRunningException'] Rails.application.routes.default_url_options[:host] = "panel.#{ENV.fetch('SUTTY', 'sutty.nl')}" Rails.application.routes.default_url_options[:protocol] = 'https' diff --git a/config/locales/en.yml b/config/locales/en.yml index 686fa212..53dd25c6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -171,6 +171,7 @@ en: usuarie: User licencia: License design: Design + indexed_post: Indexed post attributes: usuarie: email: 'E-mail address' @@ -199,6 +200,10 @@ en: layout_incompatible: error: "Design can't be changed because there are posts with incompatible layouts" help: "Your site has posts with layouts only compatible with the current design. If you change it, the site won't work as you expect. If you're trying out designs, you can delete posts in the following incompatible layouts:: %{layouts}." + usuarie: + attributes: + lang: + not_available: "This language is not yet available, would you help us by translating Sutty into it?" errors: argument_error: 'Argument `%{argument}` must be an instance of %{class}' unknown_locale: 'Unknown %{locale} locale' @@ -420,6 +425,8 @@ en: title: 'Edit %{site}' submit: 'Save changes' btn: 'Configuration' + update: + post: "Your changes have been saved. **If you enabled a publication method, don't forget to publish changes.**" form: errors: title: There were errors and we couldn't save your changes :( @@ -428,7 +435,7 @@ en: name: "This will be the host name for your site, ie. **example**.sutty.nl. Choose an expression up to 63 characters. It can contain only lowercase letters, numbers and dashes, **and no spaces**. It can't start or end with a dash, or be entirely composed of numbers." title: 'The title can be anything you want' description: 'You site description that appears in search engines. Between 50 and 160 characters.' - design: 'Select the design for your site. You can change it later. We add more designs from time to time!' + design: 'Select the design for your site. We add more designs from time to time!' licencia: 'Everything we publish has automatic copyright. This means nobody can use our works without explicit permission. By using licenses, we stablish conditions by which we want to share @@ -479,6 +486,9 @@ en: success: 'Site upgrade has been completed. Your next build will run this upgrade :)' error: "There was an error when trying to upgrade your site. This could be due to conflicts that couldn't be solved automatically. A report of the issue has already been sent to our admins. Sorry for the inconvenience! :(" message: 'Skeleton upgrade' + webhooks: + pull: + message: 'Webhooks pull' footer: powered_by: 'is developed by' i18n: @@ -700,7 +710,7 @@ en: new: 'Create' edit: 'Configure' posts: - new: 'New %{layout}' + new: 'Add %{layout}' edit: 'Editing' usuaries: index: 'Users' @@ -724,3 +734,5 @@ en: build_stats: index: title: "Publications" + indexed_posts: + deleted: "Deleted indexed post %{path} from %{site} (records: %{records})" diff --git a/config/locales/es.yml b/config/locales/es.yml index c589880a..7a8ac738 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -171,6 +171,7 @@ es: usuarie: Usuarie licencia: Licencia design: Diseño + indexed_post: Artículo indexado attributes: usuarie: email: 'Correo electrónico' @@ -199,6 +200,10 @@ es: layout_incompatible: error: 'No se puede cambiar la plantilla porque hay artículos con formatos incompatibles' help: 'En tu sitio hay artículos que solo son compatibles con el diseño actual, si cambias la plantilla el sitio no funcionará como esperas. Si estás probando plantillas, puedes eliminar los artículos en los formatos incompatibles: %{layouts}.' + usuarie: + attributes: + lang: + not_available: "Este idioma todavía no está disponible, ¿nos ayudas a agregarlo y mantenerlo?" errors: argument_error: 'El argumento `%{argument}` debe ser una instancia de %{class}' unknown_locale: 'El idioma %{locale} es desconocido' @@ -426,6 +431,8 @@ es: title: 'Editar %{site}' submit: 'Guardar cambios' btn: 'Configuración' + update: + post: "Tus cambios han sido guardados. **Si agregaste un método de publicación, no te olvides de publicar cambios.**" form: errors: title: Hubo errores y no pudimos guardar tus cambios :( @@ -434,7 +441,7 @@ es: name: 'El nombre de tu sitio que formará parte de la dirección (**ejemplo**.sutty.nl). Solo puede contener hasta 63 letras minúsculas, números y guiones, pero **sin espacios**. No puede empezar ni terminar con guión, ni estar compuesto enteramente por números.' title: 'El título de tu sitio puede ser lo que quieras.' description: 'La descripción del sitio, que saldrá en buscadores. Entre 50 y 160 caracteres.' - design: 'Elegí el diseño que va a tener tu sitio aquí. Podés cambiarlo luego. De tanto en tanto vamos sumando diseños nuevos.' + design: 'Elegí el diseño que va a tener tu sitio aquí. De tanto en tanto vamos sumando diseños nuevos.' licencia: 'Todo lo que publicamos posee automáticamente derechos de autore. Esto significa que nadie puede hacer uso de nuestras obras sin permiso explícito. Con las licencias establecemos @@ -487,6 +494,9 @@ es: success: 'Ya se incorporaron los cambios en el sitio, se aplicarán en la próxima compilación que hagas :)' error: 'Hubo un error al incorporar los cambios en el sitio. Esto puede deberse a conflictos entre cambios que no se pueden resolver automáticamente. Hemos enviado un reporte del problema a les administradores de Sutty para que estén al tanto de la situación. ¡Lo sentimos! :(' message: 'Actualización del esqueleto' + webhooks: + pull: + message: 'Traer los cambios a partir de un evento remoto' footer: powered_by: 'es desarrollada por' i18n: @@ -708,7 +718,7 @@ es: new: 'Crear' edit: 'Configurar' posts: - new: 'Nuevo %{layout}' + new: 'Agregar %{layout}' edit: 'Editando' usuaries: index: 'Usuaries' @@ -732,3 +742,5 @@ es: build_stats: index: title: "Publicaciones" + indexed_posts: + deleted: "Eliminado artículo %{path} de %{site} (filas: %{records})" diff --git a/config/routes.rb b/config/routes.rb index 3828915c..f2487066 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -17,6 +17,8 @@ Rails.application.routes.draw do get :'contact/cookie', to: 'invitades#contact_cookie' post :'contact/:form', to: 'contact#receive', as: :contact + + post :'webhooks/pull', to: 'webhooks#pull' end end end diff --git a/db/migrate/20230519143500_add_pagination_to_site.rb b/db/migrate/20230519143500_add_pagination_to_site.rb new file mode 100644 index 00000000..387dc588 --- /dev/null +++ b/db/migrate/20230519143500_add_pagination_to_site.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Agrega la opción de paginación a los sitios +class AddPaginationToSite < ActiveRecord::Migration[6.1] + def change + add_column :sites, :pagination, :boolean, default: false + end +end diff --git a/db/migrate/20230731195050_add_token_to_roles.rb b/db/migrate/20230731195050_add_token_to_roles.rb new file mode 100644 index 00000000..c38b0526 --- /dev/null +++ b/db/migrate/20230731195050_add_token_to_roles.rb @@ -0,0 +1,12 @@ +class AddTokenToRoles < ActiveRecord::Migration[6.1] + def up + add_column :roles, :token, :string + Rol.find_each do |m| + m.update_column( :token, SecureRandom.hex(64) ) + end + end + + def down + remove_column :roles, :token + end +end diff --git a/db/migrate/20230927153926_add_last_indexed_commit_to_sites.rb b/db/migrate/20230927153926_add_last_indexed_commit_to_sites.rb new file mode 100644 index 00000000..71e08f37 --- /dev/null +++ b/db/migrate/20230927153926_add_last_indexed_commit_to_sites.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# Almacenar el último commit indexado +class AddLastIndexedCommitToSites < ActiveRecord::Migration[6.1] + def up + add_column :sites, :last_indexed_commit, :string, null: true + + Site.find_each do |site| + site.update_columns(last_indexed_commit: site.repository.head_commit.oid) + rescue Rugged::Error, Rugged::OSError => e + puts "Falló #{site.name}, ignorando: #{e.message}" + end + end + + def down + remove_column :sites, :last_indexed_commit + end +end