<style> .reveal h2 { text-align:center; font-size: 50px; color: #fd3; margin-bottom: 8px; } p.part { text-align: left; font-size: 20px; } .reveal .code-wrapper { border-radius: 4px; width: 100%; } .code-wrapper > *::-moz-selection { color: #222; background: #fd3; } .path { color: #fd3; } .comando { background: #333; border-radius: 4px; padding: 0 6px 3px 6px; } .comando::before { content: "> " } .language-bash::before { content: "$ " } .language-dos::before { content: "> " } .irb { background: #282828; border-radius: 4px; padding: 0 6px 3px 6px; } .irb::before { content: "irb(main):001:0> "; color: #2f3; } .code-wrapper > *::selection, .hljs-tag::selection, .hljs-tag > *::selection, .hljs-symbol::selection, .language-ruby > *::selection, .hljs > *::selection, .hljs-ln-code::selection, .hljs-ln-code > *::selection, .hljs-ln-code > * > *::selection, .hljs-ln-code > * > * > *::selection, .hljs-class > *::selection, .comando > *::selection, .irb > *::selection{ color: #222; background: #fd3; } .reveal .code-wrapper code { white-space: pre; } pre.flow-chart, pre.sequence-diagram, pre.graphviz, pre.mermaid, pre.abc, pre.vega { background: #222; border-bottom: 1px solid #ccc; width: 100%; } img { width: 100%; margin: 0 !important; } h4 { text-align: left; } .reveal ul{ text-align: left !important; font-size: 23px; } </style> ## REQ1: Set-up inicial Creación del proyecto: ```bash rails new the_rial_news -d postgresql ``` Modificar datos de conexión a través de credenciales: **BASH** <i class="fa fa-terminal"></i> ```bash EDITOR="code --wait" rails credentials:edit ``` **CMD** <i class="fa fa-terminal"></i> ```bat set EDITOR=code --wait && rails credentials:edit ``` <div style="display: flex; font-size: 32px; gap: 12px"> <div style=""> Modifica los valores de conexión ```yml= db: username: user_db password: password_db ``` </div> <div style=""> En el archivo <span class="path">config/database.yml</span> carga los valores ```yml=23 username: <%= Rails.application.credentials.db[:username] %> password: <%= Rails.application.credentials.db[:password] %> ``` </div> </div> Crear la base de datos con el comando: <span class="comando">`rails db:create`</span> --- ### Entidades y relaciones Visto desde la perspectiva de publicaciones, usuarios y administrador ```mermaid %%{ init: { 'theme': 'base', 'themeVariables': { 'background': '#fff', 'primaryColor': '#FFB439', 'primaryTextColor': '#222', 'primaryBorderColor': '#FFB439', 'tertiaryColor': '#fff' }, 'themeCSS': '.er.attributeBoxEven { fill:#ddd} .er.relationshipLine { stroke:#ccc}' } }%% erDiagram USER ||--|{ COMMENT :escribe ADMIN ||--|{ PUBLICATION :publica COMMENT ||--|{ PUBLICATION :esta ``` --- Visto desde la perspectiva de comentarios ```mermaid %%{ init: { 'theme': 'base', 'themeVariables': { 'background': '#333', 'primaryColor': '#FFB439', 'primaryTextColor': '#', 'primaryBorderColor': '#FFB439', 'tertiaryColor': '#fff' }, 'themeCSS': '.er.attributeBoxEven { fill:#333} .er.relationshipLine { stroke:#ccc} .er.entityBox { fill: #fd9 }' } }%% erDiagram COMMENT ||--|{ USER :escribe COMMENT ||--|{ PUBLICATION :esta ``` --- <div style="display: flex; justify-content: space-between"> <div style=""> 1\. añadir Devise al Gemfile: ```ruby=74 gem 'devise' ``` </div> <div style=""> 2\. actualizar la instalación: ```bash bundle ``` </div> <div style=""> 3\. instalar el generador de Devise: ```bash rails generate devise:install ``` </div> </div> Crear el modelo de usuario con el generador de Devise: ```bash rails generate devise User name role:integer ``` En el archivo de migración generado vamos agregar un valor por defecto para el rol: ```ruby=36 t.integer :role, default: 0 ``` Generar un scaffold para el modelo Publicación: ```bash rails g scaffold Publication user:references title description:text ``` Generar un modelo para Comentario: ```bash rails g model Comment user:references publication:references body:text ``` Corremos la migración: ```bash rails db:migrate ``` --- Añadimos la asociación en <span class="path">app/models/user.rb</span> : ```ruby=3 has_many :comments has_many :publications ``` Añadimos la asociación en <span class="path">app/models/publication.rb</span>: ```ruby=2 belongs_to :user has_many :comments ``` Y revisamos que tengamos la asociación en <span class="path">app/models/comment.rb</span>: ```ruby=2 belongs_to :user belong_to :publication ``` Gracias a esta asociación, ahora podemos realizar acciones comunes con las relaciones de uno a muchos. Tenemos 3 formas de crear un comentario relacionado: ```ruby p = Publication.first u = User.first Comment.create(publication: p, user: u, body: "Primer comentario") p.comments.create(user_id: u.id, body: "Segundo comentario") u.comments.create(publication: p, body: "Tercer comentario") ``` --- ## Requerimiento 2 El usuario que no se haya registrado o iniciado sesión solo podrá ver las publicaciones e ingresar a su detalle. (*Los usuarios registrados podrán dejar comentarios en las noticias, que podrán ser eliminados por parte de los administradores si no lo consideran apropiados*) Añadimos los roles usando el método [enum](https://api.rubyonrails.org/v7.0.5/classes/ActiveRecord/Enum.html) en el modelo User <span style="color: #fd3">app/models/user.rb</span>: ```ruby=2 enum :role, [:normal_user, :admin] ``` Creamos un método para verificar el tipo de usuario en <span style="color: #fd3">app/controllers/application_controller.rb</span>: ```ruby=2 def authorized_request(kind = nil) if current_user unless kind.include?(current_user.role) redirect_to publications_path, notice: "Solo el administrador puede realizar esta acción" end else redirect_to publications_path, notice: "Debes iniciar sesión como administrador" end end ``` --- Abrimos el archivo <span style="color: #fd3">app/controllers/publications_controller.rb</span> y añadimos lo siguiente: ```ruby=2 before_action :authenticate_user!, except: %i[ index show ] before_action only: %i[ new create edit update destroy ] do authorized_request(["admin"]) end ``` Abrimos el archivo <span class="path">config/routes.rb</span> para establecer las rutas: ```ruby= Rails.application.routes.draw do resources :publications do resources :comments, only: [:create, :destroy] end devise_for :users root "publications#index" end ``` Creamos un administrador desde el <span class="path">db/seeds.rb</span>: ```ruby=9 if User.all.count == 0 User.create(name: "administrador",email: "admin@gmail.com",password: "123456",password_confirmation: "123456",role: 1) end ``` Ejecutar el seed con <span class="comando">`rails db:seed`</span> --- Crear un controlador para los comentarios: ```bash rails generate controller comments ``` Abrimos el archivo <span style="color: #fd3">app/controllers/comments_controller.rb</span> y definimos el método de crear y eliminar: ```ruby=2 before_action :authenticate_user!, only: %w[ create ] before_action only: [:destroy] do authorized_request(["admin"]) end def create @comment = current_user.comments.new(comment_params) @comment.publication_id = params[:publication_id] respond_to do |format| if @comment.save format.html { redirect_to publication_path(params[:publication_id]), notice: "Comentario creado correctamente." } else format.html { render publication_path(params[:publication_id]), status: :unprocessable_entity } format.json { render json: @comment.errors, status: :unprocessable_entity } end end end def destroy @publication = Publication.find(params[:publication_id]) @comment = @publication.comments.find(params[:id]) @comment.destroy respond_to do |format| format.html { redirect_to publication_path(@publication), notice: "Comentario borrado" } format.json { head :no_content } end end private def comment_params params.require(:comment).permit(:body) end ``` Corremos el servidor para probar implementación: ```bash rails s ``` --- Si vemos, al momento de querer crear una publicación, nos envía a iniciar sesión y cuando queramos registrarnos, se puede observar que nos falta un campo en el formulario que es para el nombre del usuario: ![](https://hackmd.io/_uploads/BJfAp8Z5h.png) Usamos el generador para manipular la vista de registro de Devise: ```bash rails g devise:views users -v registrations ``` Usamos el generador para sobrescribir métodos en el controlador: ```bash rails g devise:controllers users -c registrations ``` --- Y abrimos el archivo <span class="path">app/controllers/users/registrations_controller.rb</span> y descomentamos las siguientes líneas: ```ruby=4 before_action :configure_sign_up_params, only: [:create] ``` ```ruby=13 def create super end ``` ```ruby=44 def configure_sign_up_params # cambiar 👇 devise_parameter_sanitizer.permit(:sign_up, keys: [:name]) end ``` Abrimos el archivo <span style="color: #fd3">config/routes.rb</span> para aseguranos que se use esa clase en las rutas de Devise: ```ruby= Rails.application.routes.draw do resources :publications do resources :comments, only: [:create, :destroy] end devise_for :users, controllers: { registrations: 'users/registrations' } root "publications#index" end ``` --- Abrimos el archivo <span class="path">app/views/users/registrations/new.html.erb</span> y añadimos el nuevo campo: ```erb <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name, autofocus: true, autocomplete: "name" %> </div> ``` Probamos la implementación creando un usuario en http://localhost:3000/users/sign_up : ![](https://hackmd.io/_uploads/S1lweDV52.png) Se creó sin problema con el nuevo campo en la base de datos, para comprobarlo entramos en la consola con <span class="comando">`rails console`</span> y dentro ejecutamos <span class="irb">`User.last.name`</span> --- Abrimos el archivo <span style="color: #fd3">app/controllers/publications_controller.rb</span> y modificamos el método **create**: ```ruby=27 def create @publication = Publication.new(publication_params) @publication.user = current_user # 👈 le asignamos usuario a la publicación respond_to do |format| if @publication.save format.html { redirect_to publication_url(@publication), notice: "Publication was successfully created." } format.json { render :show, status: :created, location: @publication } else format.html { render :new, status: :unprocessable_entity } format.json { render json: @publication.errors, status: :unprocessable_entity } end end end ``` Bajamos a la siguiente línea en el mismo archivo para descartar el **`:user_id`** en los strong params: ```ruby=72 def publication_params params.require(:publication).permit(:title, :description) end ``` --- Abrimos el archivo <span style="color: #fd3">app/views/publications/_form.html.erb</span> y modificamos la vista eliminando el campo de usuario: ```diff=14 - <div> - <%= form.label :user_id, style: "display: block" %> - <%= form.text_field :user_id %> - </div> ``` Y hacemos lo mismo en el archivo <span style="color: #fd3">app/views/publications/_publication.html.erb</span> y eliminamos las siguientes líneas: ```diff=2 - <p> - <strong>User:</strong> - <%= publication.user_id %> - </p> ``` Abrimos <span style="color: #fd3">app/views/publications/index.html.erb</span> y vamos al final donde se encuentra el link de crear una publicación para que sólo el administrador pueda usarlo, podemos ir añadiendo algunas clases de bootstrap: ```erb <% if current_user && current_user.role == "admin" %> <%= link_to "New publication", new_publication_path, class: "btn btn-primary" %> <% end %> ``` --- Y hacemos lo mismo en la vista <span style="color: #fd3">app/views/publications/show.html.erb</span> para esconder los controles para editar y eliminar la publicación : ```erb <p style="color: green"><%= notice %></p> <%= render @publication %> <% if current_user && current_user.role == "admin" %> <div class="btn-group"> <%= link_to "Edit this publication", edit_publication_path(@publication), class: "btn btn-warning" %> <%= link_to "Destroy this publication", @publication, class: "btn btn-danger", data: { turbo_method: :delete} %> </div> <% end %> <%= link_to "Back to publications", publications_path, class: "btn btn-primary" %> ``` Y agregamos bootstrap, la parte de CSS en <span class="path">app/views/layouts/application.html.erb</span> (línea: 8) ```html=8 <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous"> ``` Y agregamos bootstrap, la parte de CSS en <span class="path">app/views/layouts/application.html.erb</span> (línea: 15) ```html=15 <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script> ``` --- Añadimos una barra de navegación en <span class="path">app/views/shared/_nav.html.erb</span> ```erb= <nav class="navbar navbar-light navbar-expand-lg border-bottom border-2 border-secondary"> <div class="container"> <%= link_to "The rial news", root_path, class: "navbar-brand" %> <%= button_tag type: :button, class: "navbar-toggler", data: {bs_toggle: "collapse", bs_target: "#navbar"} do %> <%= tag.span class: "navbar-toggler-icon" %> <% end %> <div class="collapse navbar-collapse" id="navbar"> <div class="navbar-nav ms-auto gap-2"> <% if user_signed_in? %> <%= button_to "Sign Out", destroy_user_session_path, class: "btn btn-secondary", method: :delete %> <% else %> <%= link_to "Sign In", new_user_session_path, class: "btn btn-outline-dark" %> <%= link_to "Sign Up", new_user_registration_path, class: "btn btn-dark" %> <% end %> </div> </div> </div> </nav> ``` Y renderizamos este **partial** en <span class="path">app/views/layouts/application.html.erb</span> y agremos margen al contenido de las vistas: ```html=14 <%= render 'shared/nav' %> <div class="container"> <%= yield %> </div> ``` --- Para mostrar los comentarios dentro de una publicación, abrimos <span style="color: #fd3">app/views/publications/show.html.erb</span> y añadimos al final lo siguiente: ```erb <h3><u>Comentarios</u></h3> <%= render @publication.comments %> ``` Esto buscará un **partial** en <span style="color: #fd3">app/views/comments/_comment.html.erb</span>, lo creamos y añadimos el siguiente contenido: ```erb <p><%= comment.body %></p> Por: <strong><%= comment.user.email %></strong> <% if current_user && current_user.role == "admin" %> <%= link_to("Eliminar comentario", publication_comment_path(@publication, comment), data: {turbo_method: :delete}) %> <% end %> <hr> ``` Creamos un comentario por consola para probar implementación: ``` p = Publication.first u = User.first Comment.create(publication: p, user: u, body: "Comentario random") ``` Corremos el servidor y visitamos el detalle de la primera publicación: <span class="comando">`rails s`</span> y visitamos la URL: http://localhost:3000/publications y entramos a la primera publicación. --- Ahora abrimos <span style="color: #fd3">app/views/publications/show.html.erb</span> y añadimo lo siguiente: ```erb <h3><u>Comentarios</u></h3> <%= render @publication.comments %> <%# agregamos esta línea 👇 %> <%= render partial: 'comments/form', locals: { publication: @publication } %> ``` Creamos ese partial en <span style="color: #fd3">app/views/comments/_form.html.erb</span> y añadimos lo siguiente: ```erb <%= form_with(url: publication_comments_path(publication), method: :post, :html => {:style => "width: 500px;"}) do |form| %> <div> <%= form.label "comment[body]", "Comentar", style: "display: block" %> <%= form.text_area "comment[body]", class:"form-control" %> </div> <div> <%= form.submit "Comentar", class:"btn btn-primary my-3" %> </div> <% end %> ``` Y ahora probamos la implementación, creando un comentario desde alguna publicación existente. Por ejemplo la primera http://localhost:3000/publications/1 --- ## Requerimiento 3 Si un usuario olvida su contraseña, puede recibir un email para restablecerla {%speakerdeck mach911/contrasena-para-aplicaciones-gmail %} --- ## Requerimiento 4 Las noticias (publicaciones) deben tener sus tests funcionales **Setup para los test 📝:** Correr las migraciones para crear las tablas en la base de datos en el entorno de TESTING: ```bash rails db:migrate RAILS_ENV=test ``` Definimos dos usuarios de prueba un admin y uno normal en <span style="color: #fd3">test/fixtures/users.yml</span>: ```yml= one: email: adm@admin.com encrypted_password: $1b$221d9dbb083a7f33428d7c2a3c3198ae925614d70210e28716ccaa7cd4ddb79 name: 'admin_test' role: 1 two: email: usr@email.com encrypted_password: $1b$221d9dbb083a7f33428d7c2a3c3198ae925614d70210e28716ccaa7cd4ddb79 name: 'user_test' role: 0 ``` --- Abrimos el archivo <span style="color: #fd3">test/controllers/publications_controller_test.rb</span> y añadimos lo siguiente: ```ruby=5 include Devise::Test::IntegrationHelpers setup do @publication = publications(:one) @admin = users(:one) @user = users(:two) end ``` Luego podemos escribir el primer test a continuación de lo añadido anteriormente en el mismo archivo: ```ruby=13 test "should get index" do get publications_url assert_response :success assert_equal 200, response.status assert_equal "utf-8", response.charset end ``` Ejecutamos el test con el siguiente comando: ```bash rails t test/controllers/publications_controller_test.rb -n test_should_get_index ``` --- Abrimos el archivo <span style="color: #fd3">test/controllers/publications_controller_test.rb</span> y añadimos lo siguiente: ```ruby=20 test "should get new" do sign_in @admin get new_publication_url assert_response :success assert_equal 200, response.status assert_equal "utf-8", response.charset end ``` Ejecutamos el segundo test con el comando: ```bash rails t test/controllers/publications_controller_test.rb -n test_should_get_new ``` Revisamos los resultados: ![](https://hackmd.io/_uploads/ryDatgcq3.png) --- Abrimos nuevamente <span style="color: #fd3">test/controllers/publications_controller_test.rb</span> y añadimos lo siguiente: ```ruby=28 test "should create publication" do sign_in @admin assert_difference("Publication.count") do post publications_url, params: { publication: { description: @publication.description, title: @publication.title, user_id: @publication.user_id } } end assert_equal 302, response.status assert_redirected_to publication_url(Publication.last) end ``` Ejecutamos el tercer test con el comando: ```bash rails t test/controllers/publications_controller_test.rb -n test_should_create_publication ``` Revisamos los resultados: ![](https://hackmd.io/_uploads/SJuSIg9c2.png) --- Seguimos en <span style="color: #fd3">test/controllers/publications_controller_test.rb</span> y añadimos lo siguiente: ```ruby=37 test "should show publication" do get publication_url(@publication) assert_response :success assert_equal 200, response.status assert_equal "utf-8", response.charset end ``` Ejecutamos el cuarto test con el comando: ```bash rails t test/controllers/publications_controller_test.rb -n test_should_show_publication ``` Revisamos los resultados: ![](https://hackmd.io/_uploads/BJjXAlq9n.png) --- Seguimos con <span style="color: #fd3">test/controllers/publications_controller_test.rb</span> y añadimos lo siguiente: ```ruby=44 test "should get edit" do sign_in @admin get edit_publication_url(@publication) assert_response :success assert_equal 200, response.status assert_equal "utf-8", response.charset end ``` Ejecutamos el quinto test con el comando: ```bash rails t test/controllers/publications_controller_test.rb -n test_should_get_edit ``` Revisamos los resultados: ![](https://hackmd.io/_uploads/Bkg0ogc9h.png) --- Abrir nuevamente <span style="color: #fd3">test/controllers/publications_controller_test.rb</span> y añadimos lo siguiente: ```ruby=52 test "should update publication" do sign_in @admin patch publication_url(@publication), params: { publication: { description: @publication.description, title: @publication.title, user_id: @publication.user_id } } assert_equal 302, response.status assert_redirected_to publication_url(@publication) end ``` Ejecutamos el sexto test con el comando: ```bash rails t test/controllers/publications_controller_test.rb -n test_should_update_publication ``` Revisamos los resultados: ![](https://hackmd.io/_uploads/SkElf-99n.png) --- Antes de ir a probar el último test, debemos modificar este archivo <span style="color: #fd3">app/models/publication.rb</span> y añadimos lo siguiente: ```ruby= class Publication < ApplicationRecord belongs_to :user has_many :comments, dependent: :destroy end ``` De lo contrario si tratamos de eliminar una publicación en los test nos mostrará el siguiente error: ![](https://hackmd.io/_uploads/SkUPtLcc2.png) Con esto le indicamos que cuando se destruya el objeto en este caso la publicación, se eliminarán todos sus objetos asociados. Evidentemente no podemos especificar esta opción en el modelo **Comment** que está conectado con una asociación de **has_many** en la otra clase. Si lo hacemos, quedarán registros huérfanos en la base de datos al momento de eliminar un comentario ya que eliminará a su vez la noticia. --- Abrir <span style="color: #fd3">test/controllers/publications_controller_test.rb</span> y añadimos lo siguiente: ```ruby=59 test "should destroy publication" do sign_in @admin assert_difference("Publication.count", -1) do delete publication_url(@publication) end assert_redirected_to publications_url end ``` Ejecutamos el último test con el comando: ```bash rails t test/controllers/publications_controller_test.rb -n test_should_destroy_publication ``` Revisamos los resultados: ![](https://hackmd.io/_uploads/B1DuEWc9h.png) --- ## Requerimiento 5 Desplegar la aplicación Para hacerlo a través de [fly.io](https://fly.io/) debemos instalar un programa de consola #### <i class="fa fa-windows"></i> **Windows** Correr el siguiente comando en PowerShell: ```cmd pwsh -Command "iwr https://fly.io/install.ps1 -useb | iex" ``` #### <i class="fa fa-linux"></i> **Linux** Correr el siguiente comando en la terminal: ```bash curl -L https://fly.io/install.sh | sh ``` #### <i class="fa fa-apple"></i> macos Si tiene el administrador de paquetes [Homebrew](https://brew.sh/), puede instalar con el siguiente comando: ```bash brew install flyctl ``` --- #### Registrarse Para poder obtener una cuenta, podemos ejecutar el comando: ```bash fly auth signup ``` Esto nos llevará a la página de registro donde podemos: <i class="fa fa-google"></i> [**Registrarnos con correo electrónico**](https://fly.io/app/auth/google?state=phoenix&return_to=%2Flaunchpad) :point_left: <i class="fa fa-github"></i> [**Registrarnos con github**](https://fly.io/app/auth/github?state=phoenix&return_to=%2Flaunchpad) :point_left: También nos solicitará la información de pago por medio de una tarjeta de crédito, para efectos de las aplicaciones de rails y con bases de datos de postgres no nos generará ningún costo asi que no tendremos cargos adicionales por su uso. Ya con una cuenta podemos iniciar sesión con el siguiente comando: ```bash fly auth login ``` Esto nos abrirá el navegador en la página de iniciar sesión de Fly.io, seleccionamos la forma con la cuál nos registramos y volvemos a la terminal. Es posible que los usuarios de WSL tengan que ejecutar el siguiente comando para que esto funcione: ```bash ln -s /usr/bin/wslview /usr/local/bin/xdg-open ``` --- #### Iniciar la aplicación Al instalar el programa de consola nos proporciona una herramienta llamada **FLy Launch** que nos va ayudar a implementar ráoidamente la aplicación utilizando una imagen de Docker. Antes de invocar a **Fly Launch** debemos modificar el archivo <span style="color: #fd3">config/environments/production.rb</span> en la siguiente línea: ```ruby=36 config.assets.compile = true ``` Esto nos permitirá compilar correctamente los assets y tag helpers de rails. Si estamos desarrollando en **Windows** (no WSL) es recomendable ejecutar el siguiente comando: ```cmd bundle lock --add-platform x86_64-linux ``` Ahora podemos usar la herramienta antes mencionada con el comando <span class="comando">fly launch</span> a continuación nos va ir haciendo varias preguntas como el nombre para la aplicación. Por ejemplo :point_down: ``` ? Choose an app name (leave blank to generate one): m6-the-rial-news ``` A continuación, se le pedirá que seleccione una región: ``` ? Choose a region for deployment: Chicago, Illinois (US) (ord) ``` --- A continuación le preguntará si necesita una base de datos Postgres seleccionamos (**y**): ``` ? Would you like to set up a Postgresql database now? y ``` Luego nos dejará escoger 3 opciones, seleccionamos la primera plan **developer**. A continuación le preguntará si necesita una base de datos Redis seleccionamos que no (**N**): ``` ? Would you like to set up an Upstash Redis database now? N ``` En este punto, el programa **flyctl** crea unos scripts para la aplicación y escribe una configuración inicial en un archivo **fly.toml** y otros archivos para la máquina de Docker. Ahora sólo nos queda llevar a cabo la implementación con el comando <span class="comando">fly deploy</span>. Este proceso puede tardar varios minutos.
{"slideOptions":"{\"theme\":\"black\",\"transition\":\"slide\",\"progress\":true,\"center\":false}","description":"Creación del proyecto:","title":"The rial news","contributors":"[{\"id\":\"bbc6d0c6-5474-4bf0-8e86-464eb17a9a61\",\"add\":35711,\"del\":8488}]"}
    996 views
   owned this note