Quantcast
Channel: Planeta Código
Viewing all 2728 articles
Browse latest View live

Variable not found: La directiva @helper, ¿reencarnada en ASP.NET Core 3?

$
0
0
ASP.NET CoreHace ya algún tiempo nos preguntábamos que dónde había ido a parar la directiva @helper de Razor en ASP.NET Core, y la respuesta era simple: había desaparecido.

Como recordaréis, esta directiva era bastante útil para simplificar el código de las vistas y mejorar su legibilidad, pues permitía crear funciones reutilizables que mezclaban HTML y código de servidor, como en el siguiente ejemplo:
@* File: Test.cshtml *

@Multiplication(2)
@Multiplication(3)

@helper Multiplication(int x)
{
<h2>Multiplication table of @x</h2>
<ul>
@for (var i = 1; i <= 10; i++)
{
<li>@x * @i = @(x * i)</li>
}
</ul>
}
Hasta la versión 2.2, teníamos que conformarnos con apaños como los descritos en aquél post si queríamos emular lo que llevábamos tantos años utilizando con MVC 5 y anteriores. Y también entonces comentamos que había ciertas posibilidades de que en algún momento volviera a la vida, y éstas se han materializado, por fin, en ASP.NET Core 3.0.

Aunque quizás más bien habría que hablar de reencarnación...

¿Cómo implementar funciones con código de marcado?

La nueva forma de implementar bloques de código reutilizables mezclando C# y HTML en el interior de una vista ya no se basa en la directiva @helper. Ahora, simplemente podemos introducir código de marcado en funciones declaradas localmente o en el interior de bloques @functions de Razor.

Observad el siguiente ejemplo, donde creamos una función Multiplication() que mezcla código HTML y C# para generar una tabla de multiplicación:
@{
Multiplication(2);
Multiplication(3);
}

@functions
{
void Multiplication(int x)
{
<h2>Multiplication table of @x</h2>
<ul>
@for (var i = 1; i <= 10; i++)
{
<li>@x * @i = @(x * i)</li>
}
</ul>
}
}
Con esto podríamos considerar que tenemos de vuelta la directiva @helper, aunque sea utilizando esta nueva sintaxis. Sin embargo, hay algunos aspectos adicionales a tener en cuenta.

Por ejemplo, si la función utilizara internamente asincronía, tendríamos que definirla e invocarla de forma diferente, como en el siguiente ejemplo:
@{
await SlowMultiplication(2);
await SlowMultiplication(3);
}

@functions
{
async Task SlowMultiplication(int x)
{
await Task.Delay(5000);
<h2>Multiplication table of @x</h2>
<ul>
@for (var i = 1; i <= 10; i++)
{
<li>@x * @i = @(x * i)</li>
}
</ul>
}
}
Otro aspecto sumamente importante es que, a diferencia de la clásica directiva @helper, estas funciones renderizan directamente el contenido y no retornan ningún valor, por lo que no se pueden utilizar como expresiones directas:
<h1>Current date: @DateTime.Now</h1>
<div>
@Multiplication(1) <!-- Error: Multiplication() retorna void -->
</div>
-->
Aunque si quisiéramos simular este comportamiento siempre podríamos forzar un retorno, aunque fuera nulo o vacío, de la siguiente forma:
<h1>Current date: @DateTime.Now</h1>
<div>
@Multiplication(1)
</div>

@functions
{
string Multiplication(int x)
{
<h2>Multiplication table of @x</h2>
<ul>
@for (var i = 1; i <= 10; i++)
{
<li>@x * @i = @(x * i)</li>
}
</ul>
return string.Empty;
}
}
Por último, es interesante destacar que también podemos introducir la mezcla de código HTML y C# en funciones locales de C# implementadas dentro de la propia página Razor, como en el siguiente ejemplo:
@{ 
void ShowCurrentDate()
{
<div class="date">@DateTime.Now</div>
}

ShowCurrentDate();
}

¿Y ese tipo de funciones son reutilizables?

Pues me temo que no, al menos de momento. Hasta donde he podido ver, no existe ninguna forma para reutilizar este tipo de funciones; su ámbito es local a la página donde han sido definidas con @functions. Ni siquiera definiéndolas en vistas especiales como _ViewImports.cshtml o _ViewStart.cshtml están accesibles desde el resto de vistas.

Pero que no cunda el pánico ;) Recordad que si queremos reutilizar ese tipo de código entre varias vistas, siempre podemos utilizar cualquiera de los mecanismos que ASP.NET Core MVC proporciona para ello: vistas parciales, tag helpers, helpers HTML o view components.

Publicado en: www.variablenotfound.com.

Picando Código: Firefox Lockwise – El gestor de contraseñas seguro de Mozilla

$
0
0

Por fín empecé a usar un gestor de contraseñas. Es bueno tener contraseñas complejas y distintas para cada servicio que usemos. Así, si (cuando) uno de éstos servicios aparece en un nuevo escándalo de contraseñas filtradas al público, nuestra cuenta se ve comprometida únicamente en ese servicio.

Intenté por un tiempo vivir sin un gestor de contraseñas. De trabajos anteriores tuve experiencia con 1Password, pero no tenía extensión para Firefox, y lo usaba más que nada para el trabajo en sí. En la época de Cubox usábamos otro gestor online que no recuerdo el nombre, pero la interfaz gráfica se había quedado en los 90’s.

Finalmente Mozilla -una fundación en la que confío- lanzó un gestor de contraseñas software libre y con las características que necesito:

Firefox Lockwise

Firefox Lockwise provee un plugin para Firefox y aplicaciones móviles para Android y iOS. En el navegador web, se enchufa a la característica de “¿Desea guardar esta contraseña?” y mantiene un listado de los sitios web, usuarios y contraseñas. Para sincronizar esta información, debemos crear una cuenta gratuita Firefox Account:

Crear una cuenta Firefox te permite mantenerte seguro en Internet. La meta de Mozilla es darle poder a las personas en sus vidas conectadas. Al crear una cuenta Firefox, podemos subir archivos más grandes en Firefox Send (de manera segura y privada), mantener nuestras contraseñas bien guardadas y sincronizar sitios web y pestañas entre distintas instancias de Firefox (de una computadora a otra, del teléfono a la computadora, etc).

 

Firefox Lockwise

Otra característica interesante que tenemos en Firefox es “generar contraseña segura”. Cuando nos damos de alta en un sitio o cambiamos la contraseña de una cuenta existente, Firefox nos da la posibilidad de generar una contraseña aleatoria y segura, y guardarla ya en Lockwise. ¡Podemos tener una contraseña tan segura que ni siquiera la hemos visto!

Por otro lado están las aplicaciones móviles para Android y iOS. En ellas nos logueamos con nuestra cuenta Firefox, y las credenciales se sincronizan automáticamente, permitiéndonos acceder a las credenciales desde aplicaciones y sitios web en nuestro teléfono. Podemos configurar la aplicación para desbloquearla con nuestra huella dactilar, y también podemos habilitar el completado automático de contraseñas para que Firefox Lockwise ingrese las credenciales automáticamente en todas las aplicaciones. En el peor de los casos, podemos copiar las credenciales desde la aplicación y pegarlas en el campo de usuario y contraseña de cualquier aplicación o sitio web.

La verdad que me ha resultado bastante práctico y lo recomiendo. Recuerden siempre usar contraseñas seguras, contraseñas distintas en distintos servicios, y cuídense!

Visita Firefox Lockwise para empezar a usarlo.

Arragonán: Sobre Sistemas de Diseño

$
0
0

Estaba preparando otro post donde quería incluir una pequeña explicación de qué es un design system o sistema de diseño. Al ver que me iba extendiendo y se iba a diluir con el tema original, me he atrevido a intentar escribir sobre qué es y dar un poco mi visión sobre ello.


Hay multitud de compañías y organizaciones que han ido trabajando en sus sistemas de diseño, incluso hay muchos que han sido liberados como open source: El famosísimo Material de Google, Primer de Github, shibori3 y purple3 de Heroku, el de Atlassian, el del gobierno de UK… Así que es un tema que va surgiendo de forma recurrente hablando con gente dedicada o involucrada en la creación de productos digitales.

¿Qué es un sistema de diseño?

Por conversaciones en las que he participado y algunas lecturas sobre el tema, veo que hay interpretaciones algo distintas sobre qué es un sistema de diseño. Así que cuando tengo oportunidad de preguntarle a alguien sobre a qué se refiere con sistema de diseño encuentro que lo habitual es que se utilice como sustituto al de guía de estilo o al de librería de componentes UI. Incluso en algún caso aislado he encontrado que en realidad se referían a que tenían plantillas que alguien había hecho 😅.

Una guía de estilo o una librería de componentes son librerías de patrones perceptuales y/o funcionales, los artefactos más visibles de un sistema de diseño, pero no son el sistema de diseño en sí mismo.

Hasta donde yo sé no hay una definición canónica, yo uso como base la del libro Design Systems de Alla Kholmatova:

A set of connected patterns and shared practices, coherently organized to serve the purposes of a digital product.

Los patrones son una pata y las prácticas que se utilizan la otra, pero como en cualquier otro producto debemos marcar unas metas u objetivos además de tener una serie de principios de diseño que estén acordes, todo ello nos servirá como guía para la sistematización.

No darle un sentido claro a un sistema de diseño puede hacernos acabar con un batiburrillo de patrones que no tengan mucha consistencia entre sí, tener una variedad de patrones excesiva para resolver un mismo problema o incluso provocar conflictos entre personas por no tener claros los objetivos.

De cara a tener una comunicación efectiva entre los que construimos producto, también debemos trabajar en tener un lenguaje compartido para referirnos a los patrones del sistema de diseño para evitar confusiones.

Ya sean más descriptivos o algo más metafóricos, ese lenguaje compartido debería ser fácil de comprender por quienes construyen o utilizan el sistema de diseño para hacer producto. Deberíamos esforzarnos tratar de dar nombres que reflejen la finalidad de los patrones y no su aspecto.

Un ejemplo muy sencillito podría ser tener un patrón que se llame "call to action button" frente a "button orange big". Uno indica para qué está pensado mientras que el otro dice y está acoplado a cómo es, si cambiara algo de su aspecto su nombre perdería el sentido en el segundo caso.

Evidentemente todo esto termina en librerías de patrones para herramientas tipo Sketch, Figma, Adobe XD… enfocado a que quienes diseñen puedan reutilizarlos para poder centrarse más en la exploración de nuevos problemas que en resolver de nuevo cuestiones que ya se han resuelto anteriormente.

No tan habitual pero también necesario es tener artefactos como guías de componentes en html/css plano y librerías de componentes para el framework javascript o de la plataforma nativa de turno. Así se facilita el trabajo de quienes pasan a código frontend los diseños permitiendo entregar antes y ganando en mantenibilidad.

¿En qué nos beneficia todo esto?

  • Tener mayor consistencia tanto visual como de interacción. Al compartir principios de diseño facilitamos que los usuarios tengan una experiencia de usuario similar usando las distintas partes de un producto o productos que compartan y respeten el mismo sistema de diseño.
  • Mejorar la comunicación entre los miembros de un equipo de producto por tener un lenguaje compartido entre diseño, desarrollo, producto… En la documentación, UI kits, código, discusiones…
  • Gracias a las diferentes librerías con el tiempo permite ser más eficientes tanto al diseñar como al desarrollar. Esta eficiencia suele traducirse en permitir centrarse en resolver problemas nuevos y en entregar producto antes.

Suelo insistir en que a lo que desde luego no ayuda es a no contratar a alguien con skills de diseño, pretender que un sistema de diseño sea una bala de plata que cubra todo es bastante ingenuo. Por mucho sistema de diseño que haya, luego se ven interfaces que son auténticas aberraciones.

Además hay que tener en cuenta que un sistema de diseño debería estar en continua revisión, lo único constante es el cambio y haciendo producto digital el cambio es bastante rápido.

Picando Código: Piques Emacs: mantener el límite de caracteres por cada línea

$
0
0

Dependiendo el contexto podemos querer un límite de 80, 100, 120 caracteres por línea. Por ejemplo en algunos proyectos se sugiere usar un límite de 50 caracteres para el mensaje de un commit en Git.

Emacs tiene un modo para mantener el límite de caracteres en cada línea que escribimos: auto-fill-mode. Al activarlo, Emacs se va a encargar automáticamente de mantener cada línea dentro del límite establecido:

 

El límite depende de la variable fill-column de Emacs. Para leer más al respecto podemos usar el comando de Emacs describe-variable e ingresar fill-column. Emacs nos cuenta que es una variable definida en el código fuente C, su valor es 80 (en mi caso), y su valor original era 70.

También nos cuenta que podemos cambiar el valor de la variable presionando Ctrl-x f. Ésto va a cambiar el valor para el buffer en el que estamos trabajando. Si queremos cambiar el valor permanentemente, podemos agregar la siguiente línea a nuestro archivo de configuración:

(setq-default fill-column 80)

Si queremos formatear un párrafo existente con el límite de caracteres por línea, debemos dirigir el cursor al párrafo y presionar Alt q (fill-paragraph). Si queremos hacer lo mismo con una región: Alt x e ingresamos fill-region en el mini-buffer.

Un paquete interesante que podemos usar relacionado al ancho de columna es fill-column-indicator, una indicación gráfica de dónde se encuentra el límite de caracteres. Si -como yo- usan Spacemacs, pueden ejecutarlo automáticamente con [SPC (evil-mode) | Alt M (holy-mode)] + t (de toggles) + f (de fill-column-indicator).

Emacs fill-column-indicator

Emacs fill-column-indicator

Picando Código: Rookie Series: Nuevo DLC gratuito disponible para Horizon Chase Turbo

$
0
0

Me vengo divirtiendo mucho jugando Horizon Chase Turbo. El estilo visual del juego es genial, y la música excelente. Aquaris publicó una campaña nueva diseñada específicamente para principiantes, aprovechando su primer aniversario en Nintendo Switch y Xbox One. Rookie Series es un DLC gratuito disponible en Nintendo Switch, Playstation 4, Steam y XBox (en la PSN europea, el DLC estará disponible a partir del dia 2 de diciembre).

Horizon Chase Turbo: Rookie Series

La nueva campaña está enfocada a los jugadores jóvenes e inexpertos y me parece una excelente idea. Es una buena manera de atraer gente nueva, evitando la frustración de perder o bloquearse. Está orientada a que los jugadores puedan evolucionar a su propio ritmo. No hay tablas de puntuación, monedas coleccionables o gestión del combustible, el DLC se preocupa únicamente en que el jugador aprenda las mecánicasde la carrera. Y no tiene nada de malo pasear con tu auto de manera casual y sin preocupaciones, disfrutando de la música y los escenarios que nos ofrece Horizon Chase Turbo.

Características Principales:

  • Rookie, un vehículo totalmente nuevo.
  • 24 circuitos seleccionados del modo World Tour (2 carreras en cada localización).
  • 12 vehículos compitiendo en cada circuito con una IA menos agressiva (World Tour tiene 20 vehículos em cada pista).
  • Sin tablas de puntuación, monedas coleccionables o gestión de combustible.
  • Terminar una carrera en cualquier puesto desbloquea la siguiente.
  • Al terminar todas las carreras, el jugador recibe un Carnet de Conducir Dorado

Nuevo vehículo: RookieHorizon Chase Turbo: Rookie

Horizon Chase Turbo: Rookie Series presenta a Rookie, un auto nuevo perfecto para los jugadores que no tienen mucha experiencia con los juegos de carreras. Tiene un gran control y obtiene actualizaciones automáticas a medida que el jugador progresa en la campaña. Los jugadores también pueden elegir entre otros 4 vehículos clásicos en el juego base, desbloqueando nuevas carreras al final de cada circuito: puedes regresar a cada una libremente para entrenar y mejorar tus resultados.

Al completar las Rookie Series, los jugadores desbloquean una Licencia de Conducir Dorada, que muestra que están listos para el World Tour y otros modos de juego. Con el nuevo DLC de campaña y el modo multijugador local (¡que todavía no he tenido el gusto de probar!), Aquiris busca reunir a amigos y familiares para que puedan disfrutar de todo lo que ofrece Horizon Chase Turbo.

El juego tiene un montón de contenido de por sí, y se sigue agregando más con éstos DLC. En el World Tour voy por India, y me faltan completar Australia, China, Japón y Hawaii para terminarlo (hasta donde sé). Mientras tanto, también estoy jugando el modo torneo, habiendo completado el Amateur y corriendo el Profesional. Y en Playground siguen cambiando las campañas cada tanto. ¡Diversión para rato!

Si pudiera sugerir otro DLC, sería armar nuestro auto con un lanzamisilies y poder dispararlo a los autos de la IA cuando no nos dejan pasarlos…

Les dejo el tráiler del nuevo DLC:
YouTube Video

Blog Bitix: Implementar un segundo factor de autenticación en una aplicación web Java con Spring

$
0
0

El segundo factor de autenticación es una medida adicional en la autenticación que proporciona una notable mayor seguridad que utilizar solo un usuario y contraseña. Utilizando Spring y la aplicación para smatphone Google Authenticator se puede implementar en una aplicación Java el segundo factor de autenticación o 2FA con códigos temporales o TOTP.

Java
Spring

Comúnmente para realizar el proceso de autenticar a un usuario se ha realizado simplemente con el método de usuario y contraseña. Sin embargo, verificar la identidad mediante usuario y contraseña para algunos usuarios no es suficientemente seguro dado que los usuarios pueden elegir contraseñas débiles con pocos caracteres o sin usar una combinación que incluya letras, números y símbolos, pueden elegir contraseñas comunes muy utilizadas fáciles de adivinar con un ataque de diccionario, pueden usar la misma contraseña para varios servicios de modo que si las contraseñas de un servicio son descubiertas cualquier otro servicio que las utilice potencialmente corre un riesgo de seguridad. Usar solo usuario y contraseña no proporciona la suficiente seguridad para ciertos servicios que permiten realizar transacciones que involucra dinero, tratan datos sensibles o son servicios atractivos para ser atacados.

Para que las contraseñas sean seguras las aplicaciones en sus bases de datos guardar las contraseñas usando Salted Password Hashing, los usuarios por su parte deben utilizar un generador de contraseñas, utilizar una contraseña distinta para cada servicio y guardalas en una base de datos cifrada como KeePassXC para recordar cada una de ellas. Las contraseñas son algo que se conoce, cualquier persona que conozca la contraseña puede autenticarse, más recientemente una capa adicional de seguridad es requerir algo que se tiene, el segundo factor de autenticación o 2FA.

La aplicación Google Authenticator para dispositivos móviles Android permite utilizarse como segundo factor de autenticación, esta aplicación genera códigos con un tiempo corto de duración que son requeridos en un segundo paso de la autenticación después de introducir el usuario y contraseña. Con un segundo factor de autenticación se requiere algo que se sabe, el usuario y contraseña, y algo que se tiene, el dispositivo móvil que genera códigos con lo que aunque la contraseña quede comprometida no se podría realizar la autenticación sin poseer el segundo factor de autenticación.

Dado que los códigos de verificación tienen un tiempo de vida corto, habitualmente de 30 segundos, y acceder al generador del segundo factor de autenticación requiere acceso físico al dispositivo móvil la combinación de que las credenciales queden comprometidas es significativamente más difícil y por tanto la seguridad aumenta al mismo tiempo. Los principales servicios de internet como Google, Amazon, Twitter y otros servicios utilizados por millones de usuarios permiten ya utilizar 2FA, un fallo en su seguridad por la cantidad de usuarios e importante información que registran les supodría una muy mala imagen, pérdida de ingresos, costes, reputación, usuarios o dependiendo de la gravedad del fallo y los datos comprometidos multas millonarias.

A través de Spring Security y la librería aerogear-otp-java una aplicación Java puede implementar el segundo factor de autenticación, incluso posibilitar de que el requerimiento de solicitar segundo factor de autenticación sea opcional según la preferencia de un usuario o como forma de que los usuarios progresivamente habiliten el 2FA. El primer paso es proporcionar al usuario una clave secreta a través de un código QR que codifica una clave secreta que se utiliza para generar los códigos de verificación, el usuario debe escanearlo con la aplicación Google Authenticator con la cámara para que genere código de 6 dígitos con una validez de 30 segundos en el momento de autenticarse, este paso se realiza en el momento de registrarse o de activar el 2FA si es opcional. Con Google Authenticator el código en vez con la cámara también se puede introducir mediante el teclado si la aplicación se lo proporciona en forma de texto en vez de como imagen QR. La ventaja del código QR es que es más rápido y cómodo.

El primer paso de la autenticación utilizando 2FA es introducir el usuario y contraseña. El segundo paso es introducir el código del segundo factor de autenticación. Introducidos ambos el usuario es redirigido a la página de inicio.

Autenticación con segundo factor de autenticación
Aplicación Google Authenticator con varios generadores de códigos temporales

Validado el código del 2FA al usuario se le asignan los permisos que le corresponden en el sistema y que le otorgan permisos para realizar acciones, en este caso entrar a la página de inicio.

La implementación en código contiene las clases que representan una cuenta en el sistema, en InMemoryAccountRepository se crean dos usuarios admin y user con sus contraseñas en el ejemplo en texto plano y los roles que tiene asignados que les otorgarán permisos para realizar acciones en la aplicación.

1
2
3
4
5
6
packageio.github.picodotdev.blogbitix.spring2fa.account;publicinterfaceAccountRepository{Accountfind(Stringusername);}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
packageio.github.picodotdev.blogbitix.spring2fa.account;...@RepositorypublicclassInMemoryAccountRepositoryimplementsAccountRepository{privatestaticStringADMIN_SECRET="6YFX5TVT76OHHNMS";privateList<Account>accounts;publicInMemoryAccountRepository(){accounts=newArrayList<Account>();init();}privatevoidinit(){Accountadmin=newAccount();admin.setUsername("admin");admin.setPassword("{noop}password");admin.setAuth2fa(true);admin.setSecret(ADMIN_SECRET);admin.setRoles(Arrays.asList("ROLE_USER"));Accountuser=newAccount();user.setUsername("user");user.setPassword("{noop}password");user.setAuth2fa(false);user.setRoles(Arrays.asList("ROLE_USER"));accounts.add(admin);accounts.add(user);}@OverridepublicAccountfind(Stringusername){returnaccounts.stream().filter(account->account.getUsername().equals(username)).findFirst().orElse(null);}}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
packageio.github.picodotdev.blogbitix.spring2fa.account;...publicclassAccount{privateStringusername;privateStringpassword;privateStringsecret;privateBooleanauth2fa;privateList<String>roles;publicStringgetUsername(){returnusername;}publicvoidsetUsername(Stringusername){this.username=username;}publicStringgetPassword(){returnpassword;}publicvoidsetPassword(Stringpassword){this.password=password;}publicStringgetSecret(){returnsecret;}publicvoidsetSecret(Stringsecret){this.secret=secret;}publicBooleanisAuth2fa(){returnauth2fa;}publicvoidsetAuth2fa(Booleanauth2fa){this.auth2fa=auth2fa;}publicList<String>getRoles(){returnroles;}publicvoidsetRoles(List<String>roles){this.roles=roles;}}

La configuración de seguridad en Spring Security indica para cada URL que permisos se requieren. Para acceder a la página de contenido /home de la aplicación se requiere el rol USER, a la página de inicio de sesión /login se permite acceder a los usuario no autenticados donde introducen sus credenciales de usuario y contraseña, una vez validado el usuario y contraseña el usuario autenticado tiene el rol PRE_AUTH_USER, dependiendo de si el usuario en su prefrencia usa 2FA o no en el manejador de autenticación exitosa SecondFactorAuthenticationSuccessHandler redirige al usuario a la página /home o la página /code para intorducir el código de verificación del segundo factor autenticación. Al usuario autenticado exitosamente de forma completa se le sustituye el permiso PRE_AUTH_USER por los que tenga asignado, en el ejemplo el rol USER.

La verificación del código del segundo paso de autenticación se realiza en la clase CodeController con la clase Totp a partir del código enviado y el código secreto con el cual se generó la imagen de código QR.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
packageio.github.picodotdev.blogbitix.spring2fa.spring;...@Configuration@EnableWebSecuritypublicclassWebSecurityConfigextendsWebSecurityConfigurerAdapter{@AutowiredprivateUserDetailsServiceuserDetailsService;@AutowiredprivateAuthenticationSuccessHandlerauthenticationSuccessHandler;@BeanpublicPasswordEncoderencoder(){returnPasswordEncoderFactories.createDelegatingPasswordEncoder();}@BeanpublicAuthenticationSuccessHandlerauthenticationSuccessHandler(){returnnewSecondFactorAuthenticationSuccessHandler();}@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{http.authorizeRequests().antMatchers("/static/**").permitAll().antMatchers("/code").hasRole("PRE_AUTH_USER").antMatchers("/home").hasRole("USER").anyRequest().authenticated();http.formLogin().loginPage("/login").permitAll().successHandler(authenticationSuccessHandler);http.logout().permitAll();}@AutowiredpublicvoidregisterAuthentication(AuthenticationManagerBuilderauth)throwsException{auth.userDetailsService(userDetailsService);}}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
packageio.github.picodotdev.blogbitix.spring2fa.spring;...@EnableWebMvc@ConfigurationpublicclassWebMvcConfigimplementsWebMvcConfigurer{@OverridepublicvoidaddViewControllers(ViewControllerRegistryregistry){registry.addViewController("/login").setViewName("login");registry.addViewController("/code").setViewName("code");registry.addViewController("/home").setViewName("home");}@OverridepublicvoidaddResourceHandlers(ResourceHandlerRegistryregistry){registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");}@BeanpublicClassLoaderTemplateResolvertemplateResolver(){ClassLoaderTemplateResolverresult=newClassLoaderTemplateResolver();result.setPrefix("templates/");result.setSuffix(".html");result.setTemplateMode("HTML");returnresult;}@BeanpublicSpringTemplateEnginetemplateEngine(ClassLoaderTemplateResolvertemplateResolver){SpringTemplateEnginetemplateEngine=newSpringTemplateEngine();templateEngine.addDialect(newLayoutDialect());templateEngine.setTemplateResolver(templateResolver);returntemplateEngine;}@BeanpublicThymeleafViewResolverviewResolver(SpringTemplateEngineengine){ThymeleafViewResolverviewResolver=newThymeleafViewResolver();viewResolver.setTemplateEngine(engine);viewResolver.setCache(false);returnviewResolver;}}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
packageio.github.picodotdev.blogbitix.spring2fa.spring;...publicclassSecondFactorAuthenticationSuccessHandlerimplementsAuthenticationSuccessHandler{privateRedirectStrategyredirectStrategy=newDefaultRedirectStrategy();@OverridepublicvoidonAuthenticationSuccess(HttpServletRequestrequest,HttpServletResponseresponse,Authenticationauthentication)throwsIOException{handle(request,response,authentication);clearAuthenticationAttributes(request);}protectedvoidhandle(HttpServletRequestrequest,HttpServletResponseresponse,Authenticationauthentication)throwsIOException{StringtargetUrl=getTargetUrl(authentication);if(response.isCommitted()){return;}redirectStrategy.sendRedirect(request,response,targetUrl);}protectedStringgetTargetUrl(Authenticationauthentication){UserDetailsAdapteruserDetailsAdapter=(UserDetailsAdapter)SecurityContextHolder.getContext().getAuthentication().getPrincipal();if(userDetailsAdapter.getAccount().isAuth2fa()){return"/code";}else{Utils.setAuthentication();return"/home";}}protectedvoidclearAuthenticationAttributes(HttpServletRequestrequest){HttpSessionsession=request.getSession(false);if(session==null){return;}session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);}publicvoidsetRedirectStrategy(RedirectStrategyredirectStrategy){this.redirectStrategy=redirectStrategy;}protectedRedirectStrategygetRedirectStrategy(){returnredirectStrategy;}}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
packageio.github.picodotdev.blogbitix.spring2fa.spring;...publicclassUtils{publicstaticvoidsetAuthentication(){Authenticationauthentication=SecurityContextHolder.getContext().getAuthentication();UserDetailsAdapteruserDetailsAdapter=(UserDetailsAdapter)SecurityContextHolder.getContext().getAuthentication().getPrincipal();List<GrantedAuthority>authorities=AuthorityUtils.createAuthorityList(userDetailsAdapter.getAccount().getRoles().toArray(newString[0]));AuthenticationnewAuth=newUsernamePasswordAuthenticationToken(authentication.getPrincipal(),authentication.getCredentials(),authorities);SecurityContextHolder.getContext().setAuthentication(newAuth);}}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
packageio.github.picodotdev.blogbitix.spring2fa.spring;...publicclassUserDetailsAdapterimplementsUserDetails{privateAccountaccount;publicUserDetailsAdapter(Accountaccount){this.account=account;}@OverridepublicCollection<?extendsGrantedAuthority>getAuthorities(){returnAuthorityUtils.createAuthorityList("ROLE_PRE_AUTH_USER");}@OverridepublicStringgetUsername(){returnaccount.getUsername();}@OverridepublicStringgetPassword(){returnaccount.getPassword();}@OverridepublicbooleanisAccountNonExpired(){returntrue;}@OverridepublicbooleanisAccountNonLocked(){returntrue;}@OverridepublicbooleanisCredentialsNonExpired(){returntrue;}@OverridepublicbooleanisEnabled(){returntrue;}publicAccountgetAccount(){returnaccount;}}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
packageio.github.picodotdev.blogbitix.spring2fa.spring;...@Component@PrimarypublicclassUserDetailsServiceAdapterimplementsUserDetailsService{@AutowiredprivateAccountRepositoryaccountRepository;@OverridepublicUserDetailsloadUserByUsername(Stringusername)throwsUsernameNotFoundException{Accountaccount=accountRepository.find(username);if(account==null){thrownewUsernameNotFoundException(username);}returnnewUserDetailsAdapter(account);}}

El código QR es una imagen generada a partir del código secreto y una información adicional que al usurio le permite identificar la cuenta, hay webs que permiten decodificar una imagen QR para analizar que información incorpora, en esta la información de la cuenta Spring2FA (admin) y el secreto 6YFX5TVT76OHHNMS utilizado para generar los códigos temporales. En el HTML devuelto se incluye una imagen con la información embebida en el enlace de la imagen, la imagen se genera por un servicio de Google.

Decodificador de imágenes código QR
1
2
3
4
5
6
7
8
<!DOCTYPE html><htmlxmlns="http://www.w3.org/1999/xhtml"xmlns:th="http://www.thymeleaf.org"xmlns:layout="http://www.ultraq.net.nz/web/thymeleaf/layout"layout:decorate="~{layout}"><body>
...
<p><imgsrc="https://chart.googleapis.com/chart?chs=200x200&chld=M%7C0&cht=qr&chl=otpauth%3A%2F%2Ftotp%2FSpring2FA%20(admin)%3Fsecret%3D6YFX5TVT76OHHNMS"/></p>
...
</body></html>

Las dependencias de librerías son las siguientes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
...dependencies{implementation'org.springframework.boot:spring-boot-starter-web'implementation'org.springframework.boot:spring-boot-starter-security'implementation'org.thymeleaf:thymeleaf-spring5:3.0.11.RELEASE'implementation'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:2.4.1'implementation'org.jboss.aerogear:aerogear-otp-java:1.0.0'}

Este ejemplo está hecho con la infraestructura que proporciona Spring pero el proceso de autenticación es igualmente implementable con cualquier otro framework o librería.

Muchos de los servicios populares en internet implementan 2FA como medida de proteger las cuentas de los usuarios y la información en esos servicios. Hay bancos que como contraseña de acceso solo tienen un número de seis dígitos con el riesgo que representa sus usuarios por la importancia que tiene la banca en línea de los datos que se trata.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradlew run.

Variable not found: Enlaces interesantes 382

$
0
0
Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros

Publicado en Variable not found.

Variable not found: Ojo con la inicialización de propiedades usando expression bodied members

$
0
0
.NET CoreHace poco estaba revisando un proyecto y me topé con un problema derivado de un uso extraño de los expression bodied members, y creo que es interesante comentarlo porque, aunque probablemente la mayoría no os vayáis a encontrar con esto, seguro que puede venir bien a alguien que no tenga del todo claro cómo y cuándo se debe utilizar esta sintaxis.

Pero comencemos desde el principio...

Evolución de la inicialización de propiedades

Como sabemos, el siguiente código es la forma "tradicional" de definir una propiedad de sólo lectura con backing field, inicializada a un valor concreto:
public class Example
{
private int _theNumber = 42;
public int TheNumber
{
get
{
return _theNumber;
}
}
}
C# 3 introdujo las propiedades automáticas, que eliminaban la necesidad de definir explícitamente un backing field. Esto simplificaba algo el código, pero su inicialización debíamos implementarla en el constructor:
public class Example
{
public int TheNumber { get; }
public Example()
{
TheNumber = 42;
}
}
Afortunadamente, C# 6 introdujo la posibilidad de inicializar directamente propiedades automáticas junto a su declaración, lo que permitía obtener el mismo resultado escribiendo un código más compacto:
public class Example
{
public int TheNumber { get; } = 42;
}
Y al mismo tiempo, C# 6 introdujo los expression bodied members, que luego fueron extendidos en C# 7 para permitir una sintaxis aún más concisa en determinados escenarios. Usando esta característica, el código anterior podríamos dejarlo en:
public class Example
{
public int TheNumber => 42;
}

¿Y cuál es el problema?

Ciertamente esta forma de inicializar propiedades usando expression bodied members es muy cómoda y compacta, pero puede confundir un poco si no sabemos exactamente qué estamos haciendo.

Imaginemos ahora el siguiente código, donde se utiliza => con el objetivo de inicializar una propiedad:
public class Example: IDisposable
{
public DatabaseConnection Connection => Database.OpenConnection(); // Mal!
public void AddOneHundredCustomers()
{
for(var i=1; i <= 100; i++)
{
Connection.AddCustomer(new Customer() { Id = i })
}
}
public void Dispose()
{
Connection.Dispose();
}
}
Aunque la intención en el uso de => era inicializar la propiedad, en realidad lo que estamos haciendo es instanciar un objeto DatabaseConnection cada vez que se accede a la propiedad Connection, tanto en el cuerpo de AddOneHundredCustomers() como en Dispose(). Es decir, durante la ejecución del siguiente código instanciaríamos 101 conexiones, y liberaríamos sólo la última de ellas:
using (var example = new Example())
{
example.AddOneHundredCustomers();
}
Lo importante aquí es entender que al usar esa sintaxis no estamos inicializando nada, sino indicando la expresión que retornará el valor de la propiedad cada vez que accedamos a ella.

Entonces, ¿cuál sería la forma correcta de hacerlo?

Pues como siempre ;) Por una parte declaramos la propiedad y luego la inicializamos en el constructor de la clase, o bien utilizamos los inicializadores de propiedades, como en el siguiente ejemplo:
public class Example : IDisposable
{
public DatabaseConnection Connection { get; } = Database.OpenConnection(); // Ok
...
}
En conclusión, cuando se introducen novedades al lenguaje, a los desarrolladores nos encanta comenzar a utilizarlas enseguida, a veces sin llegar a entender bien qué es lo que estamos haciendo.

Y la moraleja de esta historia es que cualquier novedad, por insignificante que parezca, requiere dedicar al menos unos minutos de estudio y asimilación, pues esto puede marcar la diferencia entre una aplicación que funciona y otra que no.

Publicado en Variable not found.

Fixed Buffer: Novedades de C# 8: Índices y Rangos

$
0
0
La imagen muestra el logotipo de c# 8.0

Ya son varias semanas hablando de C# 8, y hoy le toca el turno a los Índices y Rangos. En semanas anteriores hemos hablado sobre los Streams asíncronos o el nuevo pattern matching y su potencia, ahora es el momento de explorar que mejoras tenemos para operar colecciones.

Índices en C# 8

La primera de las dos opciones, los índices, nos va a permitir acceder al índice de una colección como su propio nombre indica. Vaya… ¿Esto es nuevo? Pues sí, la novedad es que se ha introducido una estructura «Index» dentro de «System.Index». Esta estructura nos va a permitir acceder a una posición desde el inicio (como toda la vida) o desde el final utilizando ‘^‘. Esta es precisamente la novedad con los índices. Imagina esta matriz:

var array = new string[]
{
    "Hola",
    "¿Cómo estás?",
    "Bien. ¿Y tú?",
    "Muy bien. Muchas gracias",
    "Adiós"
};

Hasta ahora, podíamos acceder a su índice simplemente haciendo algo como esto:

var primero = array[0];
var último = array[array.Lenght -1];

Utilizando índices, vamos a poder vamos a poder hacer algo como esto:

var indexPrimero = new Index(0);
var indexFin = new Index(1,true);
var primero = array[indexPrimero];
var último = array[indexFin];

Esto como tal es poco práctico, ya que estamos necesitando el doble de código, pero podemos colocar directamente el valor del índice:

var primero = array[0];
var último = array[^1];

Al trabajar con índices desde el final, hay que tener en cuenta que el símbolo ‘^’ es equivalente a la longitud del array, por lo que ‘^0’ provocará una excepción como lo haría acceder a ‘array.Lenght’

Rangos en C# 8

Hasta ahora los índices no nos están aportando especial utilidad, puesto que lo que conseguimos con ellos ya lo podíamos hacer con las herramientas que estaban disponibles. Donde van a tomar especial utilidad los índices de C# 8 es cuando los combinemos con los rangos.

Los rangos son una nueva estructura introducida en C# 8 que vamos a poder utilizar para extraer una parte de una colección. Para indicar que queremos usar un rango, vamos a poder utilizar la estructura «Range» dentro de «System.Range» o ‘..’. Si por ejemplo quisiéramos extraer las dos primeras posiciones del array, podemos hacer algo como esto:

var range = new Range(0, 2);
var sel = array[range];

var sel2 = array[0..2];

Es importante tener en cuenta que cuando definimos un rango con esta nueva característica de c# 8, el primer valor se incluye y el segundo se omite. Esto quiere decir que en el caso anterior, vamos a obtener las posiciones 0 y 1, omitiendo la dos.

Como realmente un rango esta compuesto por dos índices, vamos a poder utilizar un índice desde el final. Si por ejemplo queremos quedarnos con todos los valores menos el primero y el último, podemos hacer algo como esto:

var sel = array[1..^1];

Otra posibilidad que nos ofrecen los rangos es crear rangos abiertos:

var sel1 = array[..];       //Devuelve la colección completa
var sel2 = array[1..];      //Devuelve todos los valores desde el 1 hasta el final
var sel3 = array[..^1];    //Devuelve todos los valores desde el 0 hasta el penúltimo

¿Y dónde puedo aplicar esta nueva característica? La verdad es que no todas las colecciones implementan esta posibilidad, tienen que ser colecciones «contables» . Este tipo de colecciones contables engloban tipos como Array, List<T>, Span<T>,… y String (puedes verlos todos en el enlace anterior). Esto quiere decir que vamos a poder hacer un Substring.

var input = "Hola, buenos días";
var output = input[6..12]; //buenos

Conclusión

Esta característica de C# 8 para poder acceder a colecciones mediante índices y rangos puede aportar utilidad y legibilidad en el código, sobre todo a la hora de eliminar algunas operaciones con Linq que se limitan a obtener un fragmento secuencial de la colección. Es una herramienta built-in, por lo que vamos a tener mejor rendimiento que si usáramos Linq y es más compacto. ¿Qué te parece a ti esta característica?

**La entrada Novedades de C# 8: Índices y Rangos se publicó primero en Fixed Buffer.**

Picando Código: Datos y Cogollos – Encuentro sobre acceso a la información y transparencia en el sector agua

$
0
0

El próximo martes, 10 de diciembre de 2019 a las de 19:00 a 21:00, la comunidad de datos abiertos de Uruguay se reune en la diaria Media Lab:

Datos y Cogollos

Detalles

Llega la cuarta edición de este evento para conocer y dar a conocer lo que se está haciendo con datos abiertos, ya cumpliendo un año desde la primera edición y ahora cómo evento paralelo del Encuentro sobre acceso a la información y transparencia en el sector agua, organizado por Fundación Avina y la diaria.

¡Pero tranquilos que siguen pudiendo hablar de cualquier tema, no sólo de agua! 😬

¿Qué es esto?

Es un evento para juntar a toda la comunidad trabajando alrededor (o cerca, o con interés) del tema Datos Abiertos, conocer los proyectos que hay en la vuelta, pero sobretodo conocer a las personas que estamos en eso y compartir una charla acompañada por alguna bebida para hacerlo más informal.

Es un formato que empezó en México en 2013, impulsado por SocialTIC bajo el nombre de “Datos y Mezcales” y que hace bastante rato que teníamos ganas de traer a Uruguay.

Está organizado por la comunidad de Datos Abiertos local, así que no le pertenece a ninguna organización en particular y está más que abierto a que se sigan sumando nuevas personas y organizaciones.

¿Qué va a pasar?

Ésta es la agenda que vamos a intentar seguir:
– Bienvenida y presentación de la iniciativa
– Presentaciones relámpago ⚡ (máx. 5 min.) de proyectos y organizaciones
– Brindis y convivencia (asado, chelas, cogollos, helado de dulce de leche, etc.)

¿Cómo me anoto para presentar mi proyecto?

Comentá en este Meetup y decinos el nombre de persona que presentaría, un correo de contacto y el nombre del proyecto/organización/presentación que quieren compartir. Vamos a ir anotando la lista de oradores/as acá, donde también podés anotarte directamente si así lo preferís.

Las presentaciones pueden ser con diapositivas/video/apoyo audiovisual y solicitamos que cada persona que presente la suba a la web (ej. Google Slides) y pegue el enlace a su presentación en el documento anterior. Buscamos presentaciones y presentadores/as lo más diversas posibles, empezando por apuntar a la paridad de género, así que por favor no se ofendan si preguntamos si es posible que presenten otras personas llegado el caso. ☺

Acá podés encontrar sobre las presentaciones del Primer Datos y Cogollos.

¿Por qué “cogollos”?

Básicamente porque podemos… El evento adapta su nombre a alguna bebida típica del país y así pasó por México (Datos y Mezcales), El Salvador (Datos y Cervezas), Bolivia (Datos y Singanis), Ecuador (Datos y Bielas), Colombia (Datos y Guaros), Costa Rica (Datos y Chiliguaros), Guatemala (Datos y Tragos) y España (Datos y Cañas). Cuando le toca a Uruguay, optamos por algo que ninguna de las organizaciones amigas de todos esos países podría poner legalmente (o sea, es un poco un chiste interno).

¿Es obligatorio presentar/saber de datos abiertos/tomar/fumar porro?

No, para nada. Cada uno viene y hace lo que quiere 🤗

Picando Código: Video promocional de Intellivision de Mattel Electronics – 1978

$
0
0

Ayer se cumplieron 40 años de la salida de la consola de videojuegos Intellivision. Fue lanzada por Mattel Electronics el 3 de diciembre de 1979. Se desarrollaron juegos desde 1978 hasta 1990 cuando fue discontinuada. La marca Intellivision se niega a morir, y en 2020 verá la luz una nueva consola de la marca: Intellivision Amico.

Intellivision

Como parte del aniversario, la empresa actual decidió subir a internet un video promocional nunca antes visto por el público en general. El video es de 1978, filmado un año antes del lanzamiento oficial de la computadora y cuenta con información que cambió antes del lanzamiento. Pero resulta sumamente interesante.

“El sistema basado en computadoras que transforma la televisión en un centro interactivo para juegos, entretenimiento, autoeducación, mejora personal y procesamiento de información individual y familiar.”

“Intellivision es una serie de componentes de hardware modulares y software”, en principio una consola de videojuegos (master component) con dos controles de mando y lector de cartuchos con juegos y aplicativos de software. Cuando hablan del componente “teclado” agregado, lo describen como un teclado completo estilo máquina de escribir con 64 teclas. ¡Estilo máquina de escribir! Algo que en el momento ayudaría a venderlo al público general porque ya estarían familiarizados con máquinas de escribir. También describen el lector de cassettes (cintas con dos pistas digitales y 2 pistas de audio) y micrófono para dar comandos de voz.

Las aplicaciones educativas y de bienestar están pre-programadas para explicarte el material, y el usuario debe responder por el micrófono o teclado “en simple inglés. No hay absolutamente ninguna necesidad de tener conocimiento de lenguaje informático”. Mencionan gestión financiera familiar, salud física, una aplicación para aprender francés, una para generar menúes y presupuestos de comida para la casa, y servicios financieros de gestión de presupuesto e impuestos. Todo en la privacidad de tu propio hogar. Algo que se va perdiendo de a poco 40 años después.

Me resulta sumamente interesante leer, ver y aprender sobre distintas épocas de la computación e imaginar un presente alternativo si otros jugadores hubieran dominado los mercados y los distintos eventos que nos trajeron a la situación actual. En fin, si les interesa, acá va el video:

YouTube Video

Blog Bitix: Nube privada de documentos personales con Nextcloud y OnlyOffice

$
0
0

Empresas como Google ofrecen servicios gratuitos que los usuarios pueden utilizar, algunos de ellos a cambio de entregrarles documentos con información personal con la consiguiente potencial pérdida de privacidad. Algunos de estos servicios son sustituibles con alternativas como Nextcloud y OnlyOffice que permiten a sus usuarios ser dueños de su información ya sea utilizando una Raspberry Pi como servidor que debe ser administrada o incluso en la nube con servicios como DigitalOcean o AWS.

Nextcloud
OnlyOffice

Una cuenta de Google da acceso a múltiples servicios gratuitos, útiles y con un servicio más que correcto. Algunas de estos servicios está el de correo electrónico de GMail, la sincronización y unidad de documentos de Google Drive, calendario para apuntar citas y recordatorios con Google Calendar, el acceso a la suite ofimática colaborativa Google Docs o fotos con Google Photos. Estos son los servicios que uso de Google por su comodidad.

Los servicios en la nube permiten guardar los documentos e información fuera del dispositivo donde se usen, esto permite tener sincronizados y compartir todos los documentos entre varios dispositivos como el ordenador personal en casa, tener disponibles los documentos en el ordenador del trabajo y en un dispositivo móvil como un smartphone en cualquier lugar. El problema de los servicios en la nube es que no somos realmente propietarios de nuestra información y documentos, son entregados a esos servicios como los de Google, esto genera una pérdida de privacidad sin ser conscientes de los usos que le pueda dar Google. Para proteger nuestra privacidad hay alternativas para disponer de nuestra propia nube que proporcione la mayoría de estos servicios.

Nextcloud es un software que permite alojar en nuestro propio servidor nuestros documentos sustituyendo a varios de los servicios de Google. Nextcloud permite almacenar archivos y documentos ofimáticos, fotos, música, calendarios, un visor de PDF, editor de markdown, gestor de tareas o nuestros contactos. Permite complementos con los que añadir las funcionalidad que necesitemos como un reproductor de música o un paquete ofimático con OnlyOffice que sustituya a Google Docs.

Una Raspberry Pi 4 o una de sus 7 computadoras alternativas similares es una buena opción como servidor por su pequeño tamaño, bajo consumo, totalmente silencioso con un recomendable disipador pasivo y suficiente para ejecutar con normalidad Nexcloud con los 4 GiB de memoria del modelo con más capacidad. Hay otras placas similares o incluso se puede utilizar un Intel NUC que permiten más cantidad de memoria y sus procesadores son más capaces. Para una nube privada una Raspberry Pi es interesante por su pequeño tamaño y bajo consumo eléctrico dado que su funcionamiento sería constante.

Usando Docker y el repositorio de GitHub es sencillo iniciar el servidor de Nextcloud realizando los siguientes pasos.

  • Instalar Docker.
  • Descargar o clonar el repositorio de GitHub.
  • Iniciar con Docker Compose los contenedores de Nextcloud y OnlyOffice.
  • Acceder http://localhost y realizar la configuración incial, introducir el usuario y contraseña de administrador.
  • Ejecutar _bash setconfiguration.sh.
  • Añadir el complemento de OnlyOffice.
  • Acceder a http://localhost.

Este archivo de Docker Compose incluye Nextcloud con OnlyOffice sin usar una base de datos externa.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
version:'3'services:app:container_name:app-serverimage:nextcloud:fpmstdin_open:truetty:truerestart:alwaysexpose:-'80'-'9000'volumes:-app_data:/var/www/htmlonlyoffice-document-server:container_name:onlyoffice-document-serverimage:onlyoffice/documentserver:lateststdin_open:truetty:truerestart:alwaysexpose:-'80'-'443'volumes:-document_data:/var/www/onlyoffice/Data-document_log:/var/log/onlyofficenginx:container_name:nginx-serverimage:nginxstdin_open:truetty:truerestart:alwaysports:-80:80-443:443volumes:-./nginx.conf:/etc/nginx/nginx.conf-app_data:/var/www/htmlvolumes:document_data:document_log:app_data:mysql_data:
1
$ docker-compose up
Configuración y archivos en Nextcloud

OnlyOffice es un paquete ofimático alternativa a Microsoft Office que ofrece un editor de documentos de texto, una hoja de cálculo y una aplicación para realizar presentaciones integrables en Nextcloud. Son aplicaciones con menos opciones que las ofrecidas por Microsoft Office pero suficientes para un uso sencillo, también dispone de una versión como aplicaciones de escritorio.

Ofimática con OnlyOffice y Nextcloud alternativa a Google Docs

Otras utilidades es un reproductor de música, calendario o galería de fotos, hay un complemento para añadir estas funcionalidades.

Aplicaciones y complementos

Con WebDAV los documentos son accesibles como si fuese una unidad local proporcionando la misma funcionalidad de Google Drive. En GNOME con el explorador de archivos Nautilus es posible conectarse a dispositivo WebDAV, en el caso de Nextcloud la dirección es dav://localhost/remote.php/dav/files/admin.

Archivos en el explorador de archivos Nautilus con WebDAV y opciones de administración

Con las aplicaciones para smatphone los documentos quedan accesibles en cualquier lugar teniendo un dispositivo móvil, smartphone o tableta. Tener una nube propia que esté accesible en internet hace necesario tener un dominio propio, añadir seguridad para lo que es necesario configurar Nextcloud de modo que utilice el protocolo seguro que cifre las comunicaciones con TLS, esto requiere obtener un certificado autofirmado al menos o mejor obteniendolo de Let’s Encrypt, que proporciona certificados de forma automatizada y gratuita. Otra medida para aumentar la seguridad es utilizar un segundo factor de autenticación o 2FA.

Hay ejemplo de archivo de Docker Compose para tener Nexcloud con un certificado creado y renovado de forma automática con Let’s Encrypt a través del contenedor jrcs/letsencrypt-nginx-proxy-companion y configurando las variables de entorno LETSENCRYPT_HOST, LETSENCRYPT_EMAIL con el dominio propio para Nexcloud y un correo electrónico.

Picando Código: Piques Emacs: Contar líneas, palabras y caracteres de una región

$
0
0

En Emacs al seleccionar una región, Alt = nos muestra información de las líneas, palabras y caracteres de la selección:

 

El atajo de teclado llama a la función count-words-region. Ésta función sólo cuenta los caracteres dentro de la región. Así que si no hay una región seleccionada, nos va a dar 0 como resultado. Otra función que podemos usar para contar el buffer entero es count-words, que funciona tanto para una región como para un buffer sin regiones. Como resulta más práctica, terminé reemplazando count-words-region con count-words en mi archivo de keybindings:

(global-set-key (kbd "M-=")'count-words)

Coding Potions: Cómo crear una web de tareas usando Vue (todo app)

$
0
0

Introducción

Hoy vengo con un caso práctico para afianzar los conocimientos que hemos ido viendo a lo largo de estos artículos.

Vamos a crear la típica página web para crear tareas que se suele hacer cuando empiezas con un framework. Siempre recomiendo ir creando ejemplos de este tipo según aprendes cualquier lenguaje porque muchas cosas no las entiendes del todo hasta que no las pones en práctica.

🗺️ Hoja de ruta

  • [ ] 🏗️ Crear el proyecto de Vue
  • [ ] ✨ Crear el formulario para crear tareas
  • [ ] ✨ Mostrar las tareas
  • [ ] ✨ Hacer que al pulsar las tareas se marquen como completadas
  • [ ] 💄 Crear estilos para las tareas

Creando el proyecto de Vue

Como vimos anteriormente, la manera más sencilla de crear un proyecto de Vue es utilizando su asistente de línea de comandos llamado Vue CLI. Si todavía no lo tienes instalado en tu equipo ejecuta:

npm install-g @vue/cli

Ahora ejecuta el comando para crear el proyecto con el nombre que quieras, en mi caso “tareas”.

vue create tareas

Te preguntará si quieres crear el proyecto directamente o si quieres elegir las opciones manualmente. Yo voy a crear el proyecto mediante el método default pero tú puedes seleccionar cada cosa por separado si lo prefieres, te dejo este artículo donde explico cada opción:

Tutorial Vue CLI

Listo, proyecto creado y primera tarea completada.

[X] 🏗️ Crear el proyecto de Vue

El comando habrá creado una carpeta en el directorio en el que te encontraras en la terminal al realizar ejecutar el comando. Para acceder simplemente ejecuta cd tareas

Te recomiendo que cada vez que termines una tarea, hagas commit con los cambios para tenerlo guardado por si quieres verlos o volver a un punto anterior.

Ahora ejecuta npm run serve dentro del proyecto para comprobar que el proyecto se ha creado correctamente.

Creando el formulario con Vue

Vamos con la siguiente tarea, la de crear el formulario y para ello vamos a seguir un ciclo iterativo, es decir, poco a poco añadimos funcionalidad y por el momento no nos preocupamos por cómo se vea o si es feo o bonito.

De lo que se trata es de que funcione y más adelante mejoraremos el apartado visual.

Lo primero que voy a hacer es borrar completamente el componente HelloWorld.vue que viene por defecto al crear el proyecto.

Dentro de la carpeta /src/components voy a crear un archivo llamado TodoList.vue con la estructura básica de un componente:

<template></template><script>exportdefault{}</script><style></style>

Para que todo sea más sencillo en este componente irá toda la lógica de la aplicación.

En posteriores capítulos veremos cómo separar funcionalidad y cómo pasar información de un componente a otro pero por el momento vamos a hacer las cosas sencillas para aprender.

Ante de seguir tenemos que sustituir dentro del componente App.vue el componente de HelloWorld por el que acabamos de crear. Quedaría así:

<template><divid="app"><todo-list/></div></template><script>importTodoListfrom'./components/TodoList.vue'exportdefault{name:'app',components:{TodoList}}</script><style>#app{font-family:'Avenir',Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50;margin-top:60px;}</style>

Recuerda que vue automáticamente hace el cambio de PascalCase a kebab-case en la vista. Es decir al poner el nombre TodoList, para usarlo en el template de otro componente tendrás que usar todo-list.

Hora de crear el formulario. El formulario va a constar de un elemento <form> y dentro un input para escribir la tarea y un botón submit.

Recogeremos el valor del input y lo guardaremos en una variable. Cuando el usuario haga clic sobre el input almacenaremos la tarea en el array de tareas.

Dentro del archivo TodoList que hemos creado antes por el momento he puesto esto:

<template>
  <form id="app">
    <label class="label" for="task">Nueva tarea: </label>
    <input type="text" v-model="newTask" id="task">
      
    <input type="submit" value="Crear tarea">
  </form>
</template>

<script>
export default {
  data: () => ({
    newTask: ""
  })
};
</script>

<style>
</style>

Dentro del data he creado la variable newTask en la que se almacenará el valor que meta el usuario en el input para crear tareas.

Encima del botón de submit he puesto la variable de forma temporal para que veas que se actualiza en tiempo real su valor al escribir sin tener que hacer nada.

Lo que queda para terminar esta tarea es poder almacenar la tarea que escriba el usuario.

Para ello vamos a crear en el data una variable para almacenar todas las tareas (por defecto se crea vacío) y un método conectado el submit para que al darle al botón se guarde en el array. Vamos a ello:

<template><formid="app"@submit.prevent="createTask"><labelclass="label"for="task">Nueva tarea: </label><inputtype="text"v-model="newTask"id="task"><inputtype="submit"value="Crear tarea"></form></template><script>exportdefault{data:()=>({newTask:"",tasks:[]}),methods:{createTask(){lettask={text:this.newTask,completed:false};this.tasks.push(task);this.newTask="";console.log(this.tasks);}}};</script><style></style>

¿Recuerdas que cuando vimos los métodos hablamos de un evento @click que sirve para capturar eventos de clic en elementos del html? Si no lo recuerdas echa un vistazo a este artículo sobre ḿetodos y computadas en Vue

Bueno pues hay otro evento especial para los formularios para capturar cuando el usuario hace submit en un formulario.

Como en todos los eventos, para recoger el valor se hace con el @ delante y llamando al método que queremos que se ejecute.

Este evento tiene una particularidad y es que puedes añadir el .prevent detrás para evitar que la página se recargue cuando hacemos submit.

Dentro del método que llamamos de createTask lo que hago es crear una variable local llamada task en la que defino la estructura que tendrán todas las tasks.

Cada tarea será un objeto con dos parámetros, “texto” que será un string con la descripción de la tarea (lo que el usuario mete en el input) y “completed”, un booleano para saber si está completada (por defecto a false).

Como las propiedades del data son reactivas lo único que tenemos que hacer es meter dentro del parámetro text de la task el valor de la variable newTask que tendrá el valor introducido por el usuario.

Luego lo que se hace es meter la tarea dentro del array de tareas definido en la sección data y limpiar el valor de newTask para que cada vez que se cree una el input se limpie.

Pues listo, otra tarea completada, hora de hacer commit y pasar a la siguiente.

[X] ✨ Crear el formulario para crear tareas

Mostrar las tareas creadas

Mostrar las tareas no debería que ser muy complicado, solo tenemos que mostrar el array con la tareas (usando un bucle v-for en la vista) y definir una clase dependiendo de si la tarea se ha completado o no. Vamos con ello.

Para pintar las tareas lo hacemos con un v-for:

<template><formid="app"@submit.prevent="createTask"><labelclass="label"for="task">Nueva tarea:</label><inputtype="text"v-model="newTask"id="task"/><inputtype="submit"value="Crear tarea"/><ul><liv-for="(task, i) in tasks":key="'task' + i"></li></ul></form></template>

Recuerda añadir el parámetro único key para que vue pueda identificar cada uno de los elementos del bucle.

Por último dentro del bucle accedemos al texto de la tarea accediendo a un propiedad text (o el que hayas definido al crear tareas).

Vamos ahora con los estilos para que el usuario sepa si la tarea está completada o no.

Si recuerdas capítulos anteriores, puedes definir clases de en los elementos HTML de forma dinámica dependiendo de si una variable está a true o false.

<template><formid="app"@submit.prevent="createTask"><labelclass="label"for="task">Nueva tarea:</label><inputtype="text"v-model="newTask"id="task"/><inputtype="submit"value="Crear tarea"/></form><ul><liv-for="(task, i) in tasks":key="'task' + i":class="{completed: task.completed}"></li></ul></template><script>exportdefault{data:()=>({newTask:"",tasks:[]}),methods:{createTask(){lettask={text:this.newTask,completed:true};this.tasks.push(task);this.newTask="";console.log(this.tasks);}}};</script><stylescoped>.completed{text-decoration:line-through;color:grey;}</style>

La clase completed se añadirá dependiendo de si el objeto task que está pintando el bucle tiene la propiedad completed a true o false.

Para que veas que funciona he puesto en el método de crear tareas que por defecto las ponga con el parámetro completed a true para que todas las tareas se creen ya completadas.

Por último he añadido estilos a la clase completed. En mi caso he hecho que se pinte el texto tachado y las letras de color gris más claro para que se diferencie más, pero tu puedes poner los estilos que quieras.

Tarea completada.

[X] ✨ Mostrar las tareas

Completando tareas

Toca hacer que las tareas se completen. En mi caso he decidido que se completen al pulsar sobre ellas.

Para este ejemplo lo voy hacer con el evento de click que ya conocemos pero lo correcto y accesible sería crear un botón al lado de cada tarea.

<template><formid="app"@submit.prevent="createTask"><labelclass="label"for="task">Nueva tarea:</label><inputtype="text"v-model="newTask"id="task"/><inputtype="submit"value="Crear tarea"/></form><ul><liv-for="(task, i) in tasks":key="'task' + i":class="{completed: task.completed}"
      @click="completeTask(task.text)"></li></ul></template><script>exportdefault{data:()=>({newTask:"",tasks:[]}),methods:{createTask(){lettask={text:this.newTask,completed:true};this.tasks.push(task);this.newTask="";console.log(this.tasks);},completeTask(taskText){for(leti=0;i<this.tasks.length;i++){lettask=this.tasks[i];if(taskText===task.text){task.completed=!task.completed;}}}}};</script><stylescoped>.completed{text-decoration:line-through;color:grey;}</style>

Veamos lo que he cambiado.

En primer lugar, he llamado al evento @click dentro de cada tarea en la vista. El evento de click llama a la función completeTask pasando el texto de la tarea (lo mejor hubiera sido asignar a cada tarea un id y pasar a este función el id de la tarea a completar).

Ya en la función, simplemente lo que hago es recorrer todas las tareas en busca de la tarea que ha pulsado el usuario. Si se encuentra la tarea hace que su parámetro completed se invierta, de tal forma que si no se ha completado se complete y viceversa.

DISCLAIMER. Sí, se que esta solución no es ni de lejos la más óptima y correcta, pero he optado por esto para que sea sencillo de entender. Por ejemplo no hace falta recorrer toda la lista para encontrar una tarea, Puedes añadir un break cuando la encuentre para que el bucle no continúe o puedes usar la función find de javascript.

Por cierto, IMPORTANTE, para actualizar los valores de un array de objetos como en este caso, Vue recomiendo utilizar su método (importando Vue en el componente):

Vue.$set(array,indexOfItem,newValue)

array” es el array a editar, “indexOfItem”“ el índice del array a editar y newValue el nuevo valor.

Esto es así porque Vue tiene una limitación con los arrays al actualizar valores de forma reactivas. Esto se cambiará en futuras versiones.

Si quieres más información de esto o no te queda claro consulta la documentación de Vue:

Documentación vue sobre arrays

Otra tarea más completada, falta una.

[X] ✨ Hacer que al pulsar las tareas se marquen como completadas

Estilos

Ya solo queda añadir los estilos CSS que quieras, a tu gusto.

Aquí no hay mucho que explicar, yo he optado por añadir estos. Te dejo como ha quedado todo el componente:

<template>
  <div class="task-list">
    <h1> Tasks</h1>
    <form class="form" @submit.prevent="createTask">
      <label class="label" for="task">Nueva tarea:</label>
      <input class="input" type="text" v-model="newTask" id="task" />
      <input class="button" type="submit" value="Crear tarea" />
    </form>
    <ul class="list">
      <li
        class="task"
        v-for="(task, i) in tasks"
        :key="'task' + i"
        :class="{completed: task.completed}"
        @click="completeTask(task.text)"></li>
    </ul>
  </div>
</template>

<script>
export default {
  data: () => ({
    newTask: "",
    tasks: []
  }),
  methods: {
    createTask() {
      let task = {
        text: this.newTask,
        completed: false
      };
      this.tasks.push(task);
      this.newTask = "";
      console.log(this.tasks);
    },
    completeTask(taskText) {
      for (let i = 0; i < this.tasks.length; i++) {
        let task = this.tasks[i];
        if (taskText === task.text) {
          task.completed = !task.completed;
        }
      }
    }
  }
};
</script>
<style scoped>
.task-list {
  width: 800px;
  max-width: 100%;
  margin: 0px auto;
}
.form {
  background: white;
  border-radius: 12px;
  padding: 30px;
  box-shadow: 0px 10px 22px -1px rgba(0,0,0,0.25);
  margin-top: 10px;
}
.label {
  display: block;
  margin-bottom: 10px;
}
.input {
  height: 35px;
}
.button {
  margin-left: 20px;
  height: 35px;
  border: none;
  border-radius: 5px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .2);
  background-color: #2ecc71;
  color: #ecf0f1;
  cursor: pointer
}
.list {
  margin-top: 40px;
}
.task {
  cursor: pointer;
  margin: 10px 0;
}
.completed {
  text-decoration: line-through;
  color: lightgrey;
}
</style>

Así quedaría todo:

El diseño lo he hecho en muy rápido en 2 minutos, no me lo tomes en cuenta.

Pues todo listo, te dejo el proyecto subido a Codesanbox para que puedas jugar con él:

Proyecto completo en codesabox

Te dejo un par de ejercicios como deberes por si quieres experimentar por tu cuenta:

  • [ ] 🗑️ Botón dentro de cada tarea para poder eliminarlas completamente.
  • [ ] ✏️ Botón encima de la lista de tareas para poder marcar o desmarcar todas como completadas

Conclusiones

Espero de verdad que te haya gustado este ejemplo. Yo creo que este tipo de ejercicios vienen muy bien cuando estás aprendiendo porque no es solo teoría y se ven las cosas de otra forma.

No te quedes solo con este ejemplo, con lo que ya sabes deberías ser ya capaz de hacer cosas bastantes interesantes. Te animo a que intentes crear otros proyectos con lo que ya sabes para probar tus conocimientos.

En los siguientes episodios seguiremos investigando más funcionalidades de Vue para que puedas hacer cosas más increíbles.

Blog Bitix: Nube privada para documentos personales con Nextcloud y OnlyOffice

$
0
0

Empresas como Google ofrecen servicios gratuitos que los usuarios pueden utilizar, algunos de ellos a cambio de entregrarles documentos con información personal con la consiguiente potencial pérdida de privacidad. Algunos de estos servicios son sustituibles con alternativas como Nextcloud y OnlyOffice que permiten a sus usuarios ser dueños de su información ya sea utilizando una Raspberry Pi como servidor que debe ser administrada o incluso en la nube con servicios como DigitalOcean o AWS.

Nextcloud
OnlyOffice

Una cuenta de Google da acceso a múltiples servicios gratuitos, útiles y con un servicio más que correcto. Algunas de estos servicios está el de correo electrónico de GMail, la sincronización y unidad de documentos de Google Drive, calendario para apuntar citas y recordatorios con Google Calendar, el acceso a la suite ofimática colaborativa Google Docs o fotos con Google Photos. Estos son los servicios que uso de Google por su comodidad.

Los servicios en la nube permiten guardar los documentos e información fuera del dispositivo donde se usen, esto permite tener sincronizados y compartir todos los documentos entre varios dispositivos como el ordenador personal en casa, tener disponibles los documentos en el ordenador del trabajo y en un dispositivo móvil como un smartphone en cualquier lugar. El problema de los servicios en la nube es que no somos realmente propietarios de nuestra información y documentos, son entregados a esos servicios como los de Google, esto genera una pérdida de privacidad sin ser conscientes de los usos que le pueda dar Google. Para proteger nuestra privacidad hay alternativas para disponer de nuestra propia nube que proporcione la mayoría de estos servicios.

Nextcloud es un software que permite alojar en nuestro propio servidor nuestros documentos sustituyendo a varios de los servicios de Google. Nextcloud permite almacenar archivos y documentos ofimáticos, fotos, música, calendarios, un visor de PDF, editor de markdown, gestor de tareas o nuestros contactos. Permite complementos con los que añadir las funcionalidad que necesitemos como un reproductor de música o un paquete ofimático con OnlyOffice que sustituya a Google Docs.

Una Raspberry Pi 4 o una de sus 7 computadoras alternativas similares es una buena opción como servidor por su pequeño tamaño, bajo consumo, totalmente silencioso con un recomendable disipador pasivo y suficiente para ejecutar con normalidad Nexcloud con los 4 GiB de memoria del modelo con más capacidad. Hay otras placas similares o incluso se puede utilizar un Intel NUC que permiten más cantidad de memoria y sus procesadores son más capaces. Para una nube privada una Raspberry Pi es interesante por su pequeño tamaño y bajo consumo eléctrico dado que su funcionamiento sería constante.

Usando Docker y el repositorio de GitHub es sencillo iniciar el servidor de Nextcloud realizando los siguientes pasos.

  • Instalar Docker.
  • Descargar o clonar el repositorio de GitHub.
  • Iniciar con Docker Compose los contenedores de Nextcloud y OnlyOffice.
  • Acceder http://localhost y realizar la configuración incial, introducir el usuario y contraseña de administrador.
  • Ejecutar _bash setconfiguration.sh.
  • Añadir el complemento de OnlyOffice.
  • Acceder a http://localhost.

Este archivo de Docker Compose incluye Nextcloud con OnlyOffice sin usar una base de datos externa.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
version:'3'services:app:container_name:app-serverimage:nextcloud:fpmstdin_open:truetty:truerestart:alwaysexpose:-'80'-'9000'volumes:-app_data:/var/www/htmlonlyoffice-document-server:container_name:onlyoffice-document-serverimage:onlyoffice/documentserver:lateststdin_open:truetty:truerestart:alwaysexpose:-'80'-'443'volumes:-document_data:/var/www/onlyoffice/Data-document_log:/var/log/onlyofficenginx:container_name:nginx-serverimage:nginxstdin_open:truetty:truerestart:alwaysports:-80:80-443:443volumes:-./nginx.conf:/etc/nginx/nginx.conf-app_data:/var/www/htmlvolumes:document_data:document_log:app_data:mysql_data:
1
$ docker-compose up
Configuración y archivos en Nextcloud

OnlyOffice es un paquete ofimático alternativa a Microsoft Office que ofrece un editor de documentos de texto, una hoja de cálculo y una aplicación para realizar presentaciones integrables en Nextcloud. Son aplicaciones con menos opciones que las ofrecidas por Microsoft Office pero suficientes para un uso sencillo, también dispone de una versión como aplicaciones de escritorio.

Ofimática con OnlyOffice y Nextcloud alternativa a Google Docs

Otras utilidades es un reproductor de música, calendario o galería de fotos, hay un complemento para añadir estas funcionalidades.

Aplicaciones y complementos

Con WebDAV los documentos son accesibles como si fuese una unidad local proporcionando la misma funcionalidad de Google Drive. En GNOME con el explorador de archivos Nautilus es posible conectarse a dispositivo WebDAV, en el caso de Nextcloud la dirección es dav://localhost/remote.php/dav/files/admin.

Archivos en el explorador de archivos Nautilus con WebDAV y opciones de administración

Con las aplicaciones para smatphone los documentos quedan accesibles en cualquier lugar teniendo un dispositivo móvil, smartphone o tableta. Tener una nube propia que esté accesible en internet hace necesario tener un dominio propio, añadir seguridad para lo que es necesario configurar Nextcloud de modo que utilice el protocolo seguro que cifre las comunicaciones con TLS, esto requiere obtener un certificado autofirmado al menos o mejor obteniendolo de Let’s Encrypt, que proporciona certificados de forma automatizada y gratuita. Otra medida para aumentar la seguridad es utilizar un segundo factor de autenticación o 2FA.

Hay ejemplo de archivo de Docker Compose para tener Nexcloud con un certificado creado y renovado de forma automática con Let’s Encrypt a través del contenedor jrcs/letsencrypt-nginx-proxy-companion y configurando las variables de entorno LETSENCRYPT_HOST, LETSENCRYPT_EMAIL con el dominio propio para Nexcloud y un correo electrónico.


Blog Bitix: Comando para convertir imágenes a WebP con menor tamaño desde JPEG y PNG

$
0
0

Las velocidades de conexión actuales de 100 Mbps y de 600 Mbps en los hogares permiten no darle tanta importancia al tamaño de una imagen y aunque los dispositivos móviles también tienen unas velocidades de conexión rápidas reducir el tamaño de las imágenes puede significar que una página web cargue algo más rápido. El ahorro está en un 30% y 60% lo que en colecciones grandes de imágenes el ahorro es considerable. WebP proporciona un ahorro de tamaño en las imágenes con una calidad similar que jpg y png.

GNU
Linux

Para que las imágenes ocupen menos espacio se utilizan formatos como jpg para fotos y png utilizado en imágenes como capturas de pantalla de ordenador. En las imágenes fotográficas los píxeles adyacentes suelen tener el mismo color y formatos como jpg se basan en esto para dividir la imagen en pequeñas baldosas en las que pueda almacenar la información de menos píxeles, jpg es un formato con pérdida de calidad que sin ser excesiva es aceptable y se ve compensada con un ahorro importante en el tamaño de la imagen. El formato png es un formato sin pérdida de calidad pero con igualmente con reducido tamaño en imágenes de captura de pantalla de ordenador.

Para comprimir aún más el tamaño de las imágenes con la misma calidad apreciable Google ha desarrollado el formato de imágenes WebP. Webp soporta imágenes con pérdida de calidad para ser una alternativa a jpg, sin pérdida de calidad y imágenes con animaciones como alternativa a imágenes png con animación. La reducción en tamaño de WebP sobre jpg y png está en un 30% o 60% dependiendo de la imagen un ahorro de tamaño significativo que es útil para que por ejemplo las páginas web tengan un menor tamaño de descarga y con ello se carguen más rápido si tiene numerosas fotos e imágenes.

Los navegadores modernos como Google Chrome, Firefox, Microsoft Edge y versiones para Android ya soportan WebP en los formatos con pérdida, sin pérdida y con animaciones.

Una imagen que usé para crear una imagen reducida o vista previa tiene un tamaño de 1600 píxeles de ancho y 1067 de alto ocupando 1018 KiB, la imagen en formato png tiene un tamaño de 1426 píxeles por 947 ocupando 78 KiB.

Imágenes en formato jpg y PNG

Las mismas imágenes comprimidas con WebP ocupan 826 KiB, un 18% menos, para la original en formato jpg y 26 KiB, un 66% menos, para la original en formato png. Las reducciones de tamaño son importantes y teniendo en cuenta que es sin pérdida de calidad apreciable sobre las originales utilizar WebP como formato de imagen permite ahorrar un tamaño importante de descarga en una página web o en espacio de almacenamiento en colecciones grandes de fotografías e imágenes.

Mismas imágenes en formato WebP

ImageMagick permite convertir las imágenes entre estos formatos. Con este comando se convierten todas las imágenes jpg y png de un directorio formato WebP.

1
2
$ for f in *.png;do convert -define webp:lossless=true"$f""${f%.*}.webp";done;
$ for f in *.jpg;do convert -define webp:lossless=false"$f""${f%.*}.webp";done;

Picando Código: No a Google Chrome

$
0
0

No A Google Chrome

El sitio web No To Chrome es una campaña en contra de Google y su navegador web Chrome:

“Ya no podemos pretender que Google es una fuerza positiva en el mundo”, titula el sitio. El primer paso que cada usuario de internet puede dar para mejorar la situación es simple: Usar un navegador web mejor para reemplazar a Google Chrome y decirle a todo el mundo que haga lo mismo.

El objetivo de la página es ser un punto de partida para cualquiera que use internet y mandarle un mensaje a Google que su implacable desprecio a nuestros derechos, dignidad, democracia y comunidades no va a ser tolerado.

Entre las alternativas recomiendan varios navegadores web, pero para mí la mejor opción por lejos sigue siendo Firefox. Mozilla es una fundación sin fines de lucro encargada del desarrollo y distribución de Firefox. Pero también luchan activamente por una internet abierta y accesible para todos, poniendo a los usuarios por delante del lucro. Por esto, Firefox está desarrollado con la privacidad y protección del usuario como pieza fundamental. Otro navegador que recomiendan es Tor, que se conecta a sitios web a través de una capa de redes (como la cebolla) que mantiene el anonimato del usuario.

Incluye guías para eliminar Chrome de distintos sistemas y cómo abandonar otros servicios de Google. También cuentan con una página que explica por qué debemos estar en contra de Chrome y demás productos. Explican cómo es una empresa “malvada”, a pesar de su antiguo lema “don’t be evil”. Cómo su modelo de negocios se construyó en base al capitalismo de la vigilancia y no es nuestro amigo.

En estos tiempos es difícil estar en internet y no usar algún servicio de Google. Pero podemos aportar nuestro grano de arena con acciones simples como no usar Google Chrome en ninguno de nuestros dispositivos y empezar a usar alternativas más sanas.

Visita: No To Chrome

 

Variable not found: Enlaces interesantes 383

$
0
0
Enlaces interesantes
Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET

Azure / Cloud

Data

Machine learning / IA / Bots

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros

Publicado en Variable not found.

Variable not found: Streaming en gRPC, parte I: Streaming unidireccional

$
0
0
gRPC logo
Hace bien poco hablábamos de la introducción en .NET/C# de la interfaz IAsyncEnumerable, y de las interesantes posibilidades que abría vistas a la producción y consumo de streams de mensajes.

También hace unos días dimos un repaso al soporte para la implementación de clientes y servidores gRPC lanzado con ASP.NET Core 3. En dicho post hacíamos una pequeña introducción al desarrollo de este tipo de servicios, basados en HTTP/2 y el estándar protobuf.

Como ya comentábamos entonces, a diferencia de los tradicionales servicios tipo REST (HTTP/JSON), gRPC soporta el intercambio de datos en modo streaming, tanto unidireccional como bidireccionalmente. Es decir, en el escenario más complejo podríamos abrir un canal gRPC a través del cual un cliente podría ir enviando paquetes de información de forma asíncrona, y al mismo tiempo utilizarlo para recibir un flujo de datos continuo desde el servidor.

Hoy vamos a ver cómo se combinan estas características viendo un ejemplo de implementación de un servicio gRPC que utiliza streaming unidireccional, en sentido servidor-cliente.

Para ello crearemos un servicio generador de números en streaming que funcionará de la siguiente forma:
  • Los clientes invocarán un procedimiento del servidor suministrándole un número entero inicial y un delay expresado en segundos.
  • Como respuesta, el servidor creará un stream por el que irá enviando, según la periodicidad indicada, números consecutivos partiendo del especificado en la llamada inicial.
  • El proceso finalizará, cerrando en stream, cuando el servidor haya generado 10 números, o bien cuando el cliente sea detenido.

Implementación del lado servidor

Partiendo de un proyecto creado usando la plantilla para servicios ASP.NET Core gRPC, en primer lugar le añadiremos el contrato protobuf del servicio a implementar en el archivo Protos/NumberGenerator.proto, con el siguiente contenido:
syntax = "proto3";

option csharp_namespace = "DemoGrpc";

service NumberGenerator {
rpc Generate(GenerationOptions) returns (stream GeneratedNumber);
}

message GenerationOptions {
int32 start = 1;
int32 delaySeconds = 2;
}

message GeneratedNumber {
int32 number = 1;
int64 generatedAtTicks = 2;
}
Aunque no seamos grandes expertos en protobuf, seguro que podemos entender la especificación del servicio. El servicio NumberGenerator define un procedimiento remoto llamado Generate(), que recibe un mensaje de tipo GenerationOptions y retorna un stream de mensajes de tipo GeneratedNumber. Ambos tipos de datos están también definidos en el mismo .proto de forma bastante clara.

Partiendo de este contrato protobuf, el tooling de gRPC para .NET Core generará la clase abstracta DemoGrpc.NumberGenerator.NumberGeneratorBase al compilar el proyecto. Para implementar la lógica de nuestro servicio, simplemente debemos heredar de dicha clase y sobrescribir el método Generate(), por ejemplo como sigue:
public class NumberGeneratorService : NumberGenerator.NumberGeneratorBase
{
public override async Task Generate(
GenerationOptions request,
IServerStreamWriter<GeneratedNumber> responseStream,
ServerCallContext context)
{
var current = request.Start;
while (!context.CancellationToken.IsCancellationRequested)
{
var number = new GeneratedNumber()
{
Number = current++,
GeneratedAtTicks = DateTime.Now.Ticks
};
await responseStream.WriteAsync(number);
if (current - request.Start == 10)
break;
await Task.Delay(request.DelaySeconds * 1000);
}
}
}
Creo que el código habla por si mismo :) Como podéis ver, se trata únicamente de un bucle infinito que va "disparando" enteros a través del stream, introduciendo una espera entre mensaje y mensaje.

Sólo se contemplan dos salidas posibles para el bucle, lo que asegura la finalización de la llamada al generador: cuando el CancellationToken se activa, que indica que el cliente ha dejado de estar a la escucha, o cuando hemos generado diez números.

Bien, hecho esto ya sólo nos falta registrar el endpoint en la clase Startup de ASP.NET Core, de forma que las peticiones al servicio sean rutadas correctamente:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseRouting();
...
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<NumberGeneratorService>();
...
}
}

Implementación del lado cliente

Podríamos implementar un cliente para el servicio anterior en cualquier tipo de proyecto .NET Core, pero lo haremos en forma de aplicación de consola por simplificar.

Lo único especial que necesitamos en ella es añadir una referencia al archivo .proto que contiene la definición del servicio, así como a los siguientes paquetes NuGet:
  • Google.Protobuf
  • Grpc.Net.Client
  • Grpc.Tools
La implementación del cliente del servicio podría ser la siguiente:
class Program
{
static async Task Main(string[] args)
{
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new NumberGenerator.NumberGeneratorClient(channel);

var options = new GenerationOptions()
{
Start = 10,
DelaySeconds = 2
};
using (var response = client.Generate(options))
{
await foreach (var number in response.ResponseStream.ReadAllAsync())
{
var time = new DateTime(number.GeneratedAtTicks);
Console.WriteLine($"{number.Number} generated at {time:T}");
}
}
Console.WriteLine("Finished. Press a key to close.");
Console.ReadKey();
}
}
En este código podemos ver la fontanería necesaria para crear el cliente del servicio al que deseamos acceder. Tras ello, y quizás es la parte más llamativa del código, vemos el uso de await foreach sobre el stream asíncrono retornado por la llamada al método ReadAllAsync().

Este bucle foreach iterará mientras en el lado servidor continúe la ejecución del método invocado; en otras palabras, finalizará cuando el servicio haya generado los diez números (o bien cuando el servidor sea detenido).

Cliente y servidor en ejecución

La verdad es que es alucinante lo sencillo que resulta implementar un escenario streaming unidireccional con los componentes gRPC de ASP.NET Core, porque permiten que nos centremos en nuestro código al aislarnos de toda la complejidad que hay por debajo.

Pero aún daremos una vuelta de tuerca más: en el próximo post veremos cómo implementar streaming bidireccional, es decir, servicios donde cliente y servidor puedan enviar y recibir mensajes de forma asíncrona.

Publicado unidireccionalmente en Variable not found.

xailer.info: Xailer personal: ¡Completamente gratis!

$
0
0

Estimados usuarios de Xailer y Xbase en general,

Aprovechando estas fechas navideñas, hemos decidido ofrecer nuestra versión personal de Xailer completamente gratis y por tiempo ilimitado. Os recordamos que esta versión es completamente funcional e incluye todo lo necesario para realizar aplicaciones de gestión robustas utilizando cualquier RDD existente. Toda la potencia del IDE de Xailer, como son su rápido depurador, el soporte de intellisense y su facilidad en la creación de ejecutables, están disponibles para el programador.

Esperamos que esta oferta tenga una gran acogida y anime a muchos usuarios de [x]Harbour, Clipper y Visual FoxPro a probar nuestro entorno de desarrollo integrado para xBase. Estamos seguros que no defraudará. Os agradecemos de antemano la difusión que podáis hacer de esta noticia.

Un cordial saludo y ¡Felices Navidades!

Viewing all 2728 articles
Browse latest View live