Blog

  • IA generativa e a urgência de reconstruir nossa relação com a verdade

    IA generativa e a urgência de reconstruir nossa relação com a verdade

    A inteligência artificial generativa já deixou de ser uma promessa futurista. Ela está aqui, moldando a forma como nos comunicamos, consumimos conteúdo e, principalmente, como percebemos o que é real. Ferramentas como Sora, da OpenAI, e Veo 3, da Google DeepMind, são demonstrações claras do quão longe já chegamos na produção de vídeos realistas, com gestos, ambientes e emoções que beiram o indistinguível.

    O avanço é impressionante, mas carrega um dilema central: quando tudo pode ser simulado, em que ainda podemos confiar? Em tempos de algoritmos que replicam vozes e expressões com perfeição, a autenticidade dos registros audiovisuais, que por décadas foi nossa âncora da realidade, entra em xeque.

    O mais recente levantamento da OCDE, Truth Quest, revela que apenas 60% das pessoas no mundo conseguem identificar conteúdos falsos. No Brasil, esse índice é ainda menor (54%). E o dado mais alarmante talvez seja este: somos o país que mais confia nas redes sociais como principal fonte de informação.

    Essa combinação entre hiperconectividade e pouca capacidade de checagem nos torna alvos fáceis para a desinformação automatizada. Vídeos falsos com forte apelo emocional, criados com IA, têm sido disseminados com o objetivo de polarizar, manipular e confundir. E muitas vezes, até profissionais da tecnologia têm dificuldade em reconhecer essas simulações.

    Precisamos desenvolver o ceticismo crítico

    A desinformação via IA vai muito além da política. Ela compromete o papel do jornalismo, fragiliza a ciência e abre brechas na segurança digital. Uma pesquisa da Ipsos mostra que 51% dos brasileiros já acreditam que a IA tornará a propagação de fake news ainda mais grave, um medo justificado, e que precisa ser levado a sério.

    Diante desse novo cenário, é urgente investirmos em educação digital robusta. Uma habilidade que será cada vez mais essencial é o que chamo de ceticismo crítico: aprender a questionar o que vemos, a buscar fontes, a desconfiar da fluidez do conteúdo antes de aceitar sua veracidade.

    Empresas de tecnologia têm parte fundamental nessa equação. O Veo, por exemplo, já possui salvaguardas que evitam o uso de imagens ou vozes de personalidades públicas sem autorização. Além disso, seus vídeos também são criados com marcas d’água invisíveis que podem ser detectadas por sistemas verificadores. É bem possível que em breve as próprias redes sociais tenham seus verificadores de marcas d’água digitais para nos indicar se um vídeo foi criado por IA. Mas esse tipo de controle ainda está longe de ser universal e pode não ser suficiente.

    Precisamos evoluir para um modelo de governança internacional da IA, que equilibre inovação com responsabilidade. Isso não significa limitar o progresso, mas garantir transparência, segurança e prestação de contas em cada nova aplicação desenvolvida.

    Avanço inevitável, maturidade indispensável

    Segundo projeções da Grand View Research, o mercado global de IA generativa deve ultrapassar US$ 109 bilhões até 2030, com uma taxa de crescimento superior a 35% ao ano. O crescimento é exponencial e inevitável. O que está ao nosso alcance é como escolhemos lidar com ele.

    O medo não pode ser a resposta. O que precisamos é de preparo. Isso envolve capacitar a sociedade para detectar manipulações, interpretar sinais de distorção, checar fontes confiáveis e buscar evidências. Só assim conseguiremos transformar o caos informacional em consciência crítica.

    O fato é que a verdade já foi desfigurada por palavras, agora pode ser fabricada com som e imagem. Mas isso não precisa nos imobilizar. Pelo contrário: pode ser o ponto de partida para construirmos uma sociedade mais analítica, responsável e tecnicamente equipada.

    A IA, por si só, não é vilã. Ela pode, sim, ser uma aliada poderosa na resolução de problemas, na personalização da educação, na produtividade e até no combate à própria desinformação. Tudo depende da forma como a integramos à nossa vida, que deve ser com clareza, ética e discernimento.

    Nossa maior proteção diante da era da desinformação não será um algoritmo nem uma legislação, será nossa capacidade de refletir antes de acreditar. Devemos estar preparados para essa nova realidade onde a dúvida é um sinal de sabedoria.


    LEIA TAMBÉM

  • Protegendo credenciais e configurações com contêiner criptografado

    Protegendo credenciais e configurações com contêiner criptografado

    Eu uso um pequeno computador de bolso na mochila. Esse dispositivo serve para testes e desenvolvimento, e armazena informações sensíveis como credenciais, senhas e chaves de acesso. Eu não quero que esses dados caiam em mãos erradas. Diferentemente do notebook, que tem o HD criptografado, meu computador de bolso não usa criptografia no HD. A criptografia consome muito processamento e requer desativar o TRIM do SSD, o que prejudica o desempenho.

    Então para contornar o problema resolvi criar um contêiner criptografado para guardar apenas as informações sensíveis. Esse contêiner é montado somente quando necessário, o que aumenta a segurança do sistema.

    Criando um contêiner criptografado

    Você precisa instalar o pacote cryptsetup. Para isso, execute como root:

    sudo apt install cryptsetup
    

    Depois, crie o contêiner criptografado. Primeiro, gere um arquivo de 200MB (como exemplo):

    dd if=/dev/urandom of=secure_container.img bs=1M count=200
    

    Inicialize o LUKS:

    cryptsetup luksFormat secure_container.img
    

    Abra o contêiner:

    cryptsetup open secure_container.img secure_container
    

    Formate (ext4 como exemplo):

    mkfs.ext4 /dev/mapper/secure_container
    

    Monte o contêiner:

    mkdir -p /mnt/secure_container
    mount /dev/mapper/secure_container /mnt/secure_container
    

    Agora o diretório /mnt/secure_container está pronto para uso. Você pode mover seus arquivos sensíveis para lá e criar links simbólicos (por exemplo, do diretório ~/.ssh ou de chaves de acesso ~/.aws).

    Desmontando o contêiner

    Para desmontar, desmonte o diretório e feche o contêiner:

    umount /mnt/secure_container
    cryptsetup close secure_container
    

    Automatizando a montagem e desmontagem

    Como montar e desmontar o contêiner é muito repetitivo vale a pena crie scripts e automatizandor essa tarefa. Os scripts precisam de permissão de root para funcionar. Você pode configurar o sudo para não pedir senha para esses scripts, facilitando o uso.

    Script de montagem (mount_secure_container.sh)

    #!/bin/sh
    
    [ "root" != "$USER" ] && exec sudo $0 "$@"
    
    # Caminhos de exemplo.
    IMAGE_PATH="/home/user/secure_container.img"
    MAPPER_NAME="secure_container"
    MOUNT_POINT="/mnt/secure_container"
    
    # Abre o contêiner.
    cryptsetup open "$IMAGE_PATH" "$MAPPER_NAME" || {
        echo "Falha ao abrir o contêiner."
        exit 1
    }
    
    # Monta o contêiner.
    mount "/dev/mapper/$MAPPER_NAME" "$MOUNT_POINT" || {
        echo "Falha ao montar o contêiner."
        cryptsetup close "$MAPPER_NAME"
        exit 1
    }
    
    echo "Contêiner montado em: $MOUNT_POINT"
    exit 0
    

    Script de desmontagem (umount_secure_container.sh)

    #!/bin/sh
    
    [ "root" != "$USER" ] && exec sudo $0 "$@"
    
    IMAGE_PATH="/home/user/secure_container.img"
    MAPPER_NAME="secure_container"
    MOUNT_POINT="/mnt/secure_container"
    
    # Desmonta o contêiner.
    umount "$MOUNT_POINT" || {
        echo "Falha ao desmontar. Verifique se algum arquivo ainda está em uso."
        exit 1
    }
    
    # Fecha o contêiner.
    cryptsetup close "$MAPPER_NAME" || {
        echo "Falha ao fechar o contêiner LUKS."
        exit 1
    }
    
    echo "Contêiner desmontado e fechado."
    exit 0
    

    Script para desligar (off)

    Crie um script chamado off para garantir que o contêiner seja desmontado antes de desligar:

    #!/bin/sh
    
    [ "root" != "$USER" ] && exec sudo $0 "$@"
    
    sync
    /home/user/bin/umount_encrypted.sh
    poweroff
    

    Em seguida, configure o sudo para não pedir senha ao executar esses scripts:

    sudo visudo
    

    Adicione as linhas:

    user ALL=(ALL) NOPASSWD: /home/user/bin/mount_encrypted.sh
    user ALL=(ALL) NOPASSWD: /home/user/bin/umount_encrypted.sh
    user ALL=(ALL) NOPASSWD: /home/user/bin/off
    

    Lembre de substituir user nos scripts pelo seu nome de usuário.

    Conclusão

    Esse método mantém dados sensíveis seguros e acessíveis apenas quando necessário. O contêiner criptografado simplifica backups e facilita a transferência para outro computador. Assim, você combina segurança e praticidade no seu sistema.

  • Como hostear seu site HTML gratuitamente com GitHub Pages

    Como hostear seu site HTML gratuitamente com GitHub Pages

    Você já conhece o GitHub Pages? Esse serviço oferece hospedagem gratuita para websites estáticos. Essa ferramenta usa Fluxos de trabalho personalizáveis ​​do GitHub Action para criar e implantar seu código.

    O GitHub Pages é uma opção poderosa para armazenar conteúdo estático pelos seguintes motivos:

    • É grátis.
    • Facilita a colaboração. Qualquer um pode abrir um pull request para atualizar o site.
    • Seu repositório sincroniza com todas as alterações feitas em seu site.
    • Embora o GitHub Pages venha com um nome de domínio padrão como https://SEU-USERNAME.github.io/ , ele oferece suporte a domínios personalizados.
    • Ele usa fluxos de trabalho personalizáveis ​​do GitHub Action para compilações e implantações.

    Vamos aprender como hospedar sites estáticos criados com HTML no GitHub Pages!

    • Escolha algum repositório seu que contenha código HTML, navegue até a aba de configurações desse repositório.
    • Agora na coluna esquerda, procure e clique em Pages
    • Aqui você pode escolher entre fazer Deploy por um branch ou com GitHub Actions. Para usar a primeira opção você só tem que escolher o o branch main e clicar em Save.
    • Nesse tutorial vamos escolher a opção GitHub Actions. Essa opção irá sugerir alguns fluxos de trabalho para você com base no código em seu repositório. Você pode escolher HTML estático e clicar em configurar.
    • Clicar em configurar levará você a um fluxo de trabalho pré-criado. Sinta-se à vontade para revisar o YAML, ajustá-lo de acordo com sua preferência e confirmar o código. Eu não vou mudar nada no template para esse tutorial e vou fazer o commit.
    • Em alguns segundos, a Action começará a ser executada. Ela gerará um URL e implantará seu site estático no GitHub Pages se for bem-sucedido. Você pode ver o status da sua Action, clicando na aba actions.

    Exemplo: https://pachicodes.github.io/talks/ – Onde pachicodes é meu username e talks é o nome do repositório.

    E ta dã, seu site está no ar! Lembrando que isso só vale para páginas em HTML estático e GitHub Pages em Jekyll.

    Dica:

    Se você tiver imagens no seu site, pode ser que elas não carreguem no seu site, e isso é por uma mudança de PATHs que o GitHub Pages cria. Para corrigir isso precisamos mudar o endereço da imagem da seguinte maneira:

    Se o endereço da imagem é assets/imagens/foto.jpg, quando hospedado no GitHub Pages, mude o endereço para {“NOME DO REPOSITORIO”}/assets/imagens/foto.jpg e assim que for feito de deploy, suas imagens devem aparecer.

    Isso também serve para PDFs.

    Conclusão

    Eu adoro o GitHub pages, quase todos meus repos tem um site, porque é super prático!

    Você já conhecia o GitHub pages?

    Comenta aí com um site que você tem hospedado esse serviço!

    E se você não tem um ainda, o que está esperando? ♦


    LEIA TAMBÉM

     

  • Error Handling em Node.js com Express

    Error Handling em Node.js com Express

    Todo mundo projeta as suas aplicações para que funcionem, certo?

    Mas elas funcionam sempre?

    É claro que não!

    Aí mora uma falha de design de software muito comum: raramente os programadores pensem na manipulação dos erros das aplicações nas quais trabalham, o que gera dores de cabeça enormes no médio e longo prazo, quando o sistema já está em produção e precisamos fazer troubleshooting, mudar tecnologia de logging ou até mesmo manipular os erros de forma mais eficiente.

    Que tal aprender a fazer error handling em Express de forma elegante e que te permita evoluir a sua aplicação com qualidade ao longo do tempo?

    Esse é o assunto do tutorial de hoje!

    Atenção: este tutorial é para quem já sabe programar ao menos o básico de Node.js com Express. 

    #1 – Voltando ao Básico

    O primeiro passo é obviamente revisitar o assunto manipulação de erro em JavaScript. Basicamente quando queremos capturar um erro que possa ser lançado em certo bloco de código, usamos try/catch, certo?

    
    try{
       //seu código aqui
    }
    catch(error){
       //seu tratamento de erro aqui
    }
    

    Se um erro acontecer dentro do bloco try, o fluxo será redirecionado para o bloco catch onde você terá acesso a um objeto error com os dados do erro, permitindo que você trate, registre e devolva ao usuário uma resposta mais amigável.

    O grande problema do try/catch é…bem, que você tem de escrevê-lo em todos os pontos da sua aplicação onde não quer que um erro possa estourar e com isso prejudicar a experiência do usuário.

    Não fosse o bastante, se você estiver com uma web API em Node.js feita com Express, se você tiver o estouro de um erro sem o devido tratamento, o seu webserver pode cair! Sim, porque erros não tratados no Event Loop simplesmente podem derrubar o Event Loop! E isso não é uma característica exclusiva do Node.js ou do JavaScript, programas de computador em geral são encerrados automaticamente se der uma exceção não tratada no seu fluxo principal!

    Assim, vamos considerar um pequeno exemplo de webapi Express onde temos três rotas, uma que funciona, uma que dá erro mas o mesmo é tratado e outra que dá erro e o mesmo não é tratado.

    
    const express = require('express');
    const app = express();
    
    app.get('/teste1', (req, res, next) => {
        res.send('teste1');
    })
    
    app.get('/teste2', (req, res, next) => {
        try {
            throw new Error('teste2 deu erro');
        } catch (error) {
            console.log(error);
            res.sendStatus(500);
        }
    })
    
    app.get('/teste3', (req, res, next) => {
        throw new Error('teste3 deu erro');
    })
    
    app.listen(3000, () => {
        console.log('Server running at 3000');
    })
    

    Não esqueça de instalar a dependência do express antes de rodar este projeto com “npm install”.

    Teste as 3 rotas usando Postman ou mesmo no seu navegador. Consegue perceber a diferença entre as 3? Em especial a 2 e 3, pois na 2 conseguimos tratar o erro e dar algum destino diferente para a requisição, enquanto que na 3 estoura um erro grosseiro e que não temos controle, inclusive expondo nosso servidor ao requisitante.

    Jamais deixe suas rotas desta forma!

    #2 – Error Middleware

    Uma forma de fazer o gerenciamento de erros de forma mais profissional é centralizando-o através de um error middleware.

    O Express é todo organizado através de middlewares de processamento e existe um middleware especial que chamamos de Error Middleware que serve como destino default para todos os erros gerados e não tratados no event loop.

    Enquanto que um middleware normal possui 3 parâmetros (req, res e next), o Error Middleware é criado passando um parâmetro a mais, que na verdade deve ser o primeiro: error. Além disso, ele deve ser o último middleware na sua cadeia de processamento.

    
    app.get('/teste3', (req, res, next) => {
        throw new Error('teste3 deu erro');
    })
    
    app.use((error, req, res, next) => {
        console.log('error middleware');
        res.sendStatus(500);
    })
    

    No exemplo acima, criei o error middleware logo após a última rota. Experimente subir novamente sua aplicação e testar a rota 3. Verá que apesar de ela não possuir try/catch, seu fluxo será redirecionado para o error middleware automaticamente quando o erro estourar, simplificando bastante o tratamento de erros pois agora ele estará centralizado.

    Ainda assim, nas rotas que você desejar ainda ter um try/catch personalizado, você pode mantê-lo e redirecionar para o error middleware opcionalmente, com a função next, passando o erro como único argumento.

    
    app.get('/teste2', (req, res, next) => {
        try {
            throw new Error('teste2 deu erro');
        } catch (error) {
            console.log(error);
            next(error);
        }
    })
    

    A partir da v5 do Express você também tem suporte a captura de erros assíncronos, então pode testar o código abaixo que também vai funcionar.

    
    app.get('/teste3', async (req, res, next) => {
        throw new Error('teste3 deu erro');
    })
    

    Como próximos passos, que eu não vou abordar hoje pois este tutorial já está se tornando extenso, você pode utilizar Custom Errors na sua aplicação para que o Error Middleware consiga identificar os tipos de erro e dar retornos apropriados.

  • Arquitetura de Microsserviços em Node com o MoleculerJS

    Arquitetura de Microsserviços em Node com o MoleculerJS

    Montar um projeto na arquitetura de Microsserviços não é tão trivial e nem tão necessário quanto a maior parte das pessoas desenvolvedoras iniciantes imaginam que seja.

    Muitos escutam o termo e, por ele estar na moda, já acham que precisam sair aplicando sem mesmo entender o que ela é, pra que serve e porque faria sentido (ou não) usá-la. Na vida real não é bem assim que funciona.

    A arquitetura de microsserviços é incrível e pode resolver vários complexos. Mas aí é que está o X da questão: quais problemas complexos que ele resolve? Dificilmente um projeto pessoal pequeno terá qualquer tipo desses problemas.

    O caminho natural das nossas aplicações é ser construída em um monolito até que faça sentido mudar a sua arquitetura. Isso se fizer sentido. Você sabia que o próprio Stack Overflow é um monolito?

    Dito isso, estudar esse tipo de arquitetura e aplicá-la para aprender seus conceitos e detalhes é sim muito interessante. Experimentar um pouco dessa arquitetura te ajudará a ganhar experiência para então tomar melhores decisões no futuro.

    Como então fazer para aplicar essa arquitetura dentro do ecossistema Node?

    Como tudo na tecnologia, há várias formas diferentes; no entanto, recentemente encontrei uma bem interessante por meio do framework Moleculer.

    Bora explorá-lo?

    O Framework Moleculer

    • O Moleculer é um framework para Node.js que nos ajuda a criar aplicações usando a arquitetura de microsserviços.
    • Aplicar a arquitetura de microsserviços não se resume a apenas quebrar a aplicação em várias menores. Existem conceitos e técnicas para conseguir fazer isso da melhor forma possível, e o framework já vem integrado com vários deles, como por exemplo:
    • API Gateway;
    • Load Balancer;
    • Fault Tolerance;
    • Service Discovery;
    • Etc.
    • Não vale a pena ficar explicando cada um desses pontos agora porque esse assunto é bem denso. Vamos tentar montar um projetinho na prática com essa arquitetura e naturalmente descobriremos o que eles são e como usá-los.
    • Combinado?

    Aplicação prática

    • Como eu disse, o caminho natural de uma aplicação é nascer do monolito e, a medida que sua complexida se tornar muito grande, a “quebramos” em aplicações menores.
    • Partindo dessa ideia, vamos começar da seguinte aplicação:
    • Para a coisa não ficar muito complexa, existem apenas três rotas: uma para criação de usuário, outra para fazer o usuário entrar na aplicação e uma terceira rota protegida (exige autenticação).
    • Vou propor separamos essa aplicação em dois microsserviços distintos, uma para cuidar da parte de criação, cadastro e login dos usuários; e o outra para lidar com os produtos.
    • Note que em um arquitetura em microsserviços temos uma interface que centralizará todas as requisições e “distribuirá” para os respectivos serviços. Aqui ela está representada pelo app no diagrama. Esta peça é o que chamamos de Gateway.
    • Repare também que nesta organização, cada aplicação tem o seu próprio banco de dados. A ideia é que os serviços sejam independentes, até mesmo nos dados. Mas nada impede que essas aplicações se comuniquem.
    • Essa comunicação pode acontecer de várias formas, desde chamadas http (pouco usado) até serviços de mensageria (muito usado). Em nossa aplicação usaremos um serviço externo chamado NATS que vai nos ajudar com isso.

    Para começar a implementação, faremos 3 projetos em node (farei na mesma pasta pra facilitar):

    • api-gateway: npm i moleculer moleculer-web nats
    • auth-service: npm i moleculer moleculer-replt nats uuid
    • product-service: npm i moleculer moleculer-replt nats

    Vamos começar a construção de cada uma das peças da nossa aplicação.

    • API Gateway
    import { ServiceBroker } from "moleculer";
    import ApiGatewayService from "moleculer-web";
    
    const broker = new ServiceBroker({
      nodeID: "gateway-node",
      transporter: "NATS",
    });
    
    broker.createService({
      name: "gateway",
      mixins: [ApiGatewayService],
      settings: {
        port: 5000,
        routes: [
          {
            path: "/auth",
            aliases: {
              "POST /sign-up": "auth.signUp",
              "POST /sign-in": "auth.signIn"
            }
          },
          {
            path: "/api",
            bodyParsers: {
              json: true,
              urlencoded: { extended: true }
            },
            authorization: true,
            aliases: {
              "GET /products": "product.getProducts"
            }
          }
        ]
      },
      methods: {
        async authorize(ctx, route, req, res) {
          let auth = req.headers["authorization"];
          if (auth && auth.startsWith("Bearer")) {
            let token = auth.slice(7);
    
            const user = await ctx.call("auth.validateToken", { token });
            if (!user.error) return Promise.resolve(ctx);
    
            return Promise.reject({ error: "Token Inválido" });
    
          } else {
            return Promise.reject({ error: "Token Inválido" });
          }
        }
    
      }
    });
    
    broker.start();
    • Auth Service
    import { ServiceBroker } from "moleculer";
    import { v4 as uuidv4 } from 'uuid';
    
    const users = [];
    
    const broker = new ServiceBroker({
      nodeID: "auth-service-node",
      transporter: "NATS"
    });
    
    broker.createService({
      name: "auth",
      actions: {
        signUp(ctx) {
          const { username, password } = ctx.params;
          users.push({
            username,
            password
          });
    
          return "User created."
        },
    
        signIn(ctx) {
          const { username, password } = ctx.params;
          const user = users.find(user => user.username === username);
          if (user && user.password === password) {
            const token = uuidv4();
            user.token = token;
            return {
              token
            }
          }
    
          return { error: "Invalid credentials" }
        },
    
        validateToken(ctx) {
          const { token } = ctx.params;
          const user = users.find(user => user.token === token);
          if (!user) return { error: "Invalid token." }
    
          return user;
        }
      }
    });
    
    broker.start().then(() => {
      broker.repl();
    })
    • Products Service
    import { ServiceBroker } from "moleculer";
    
    const products = [
      { id: 1, name: "PC da Xuxa", price: 15000 }
    ]
    
    const broker = new ServiceBroker({
      nodeID: "product-service-node",
      transporter: "NATS"
    });
    
    broker.createService({
      name: "product",
      actions: {
        getProducts(ctx) {
          return products;
        }
      }
    });
    
    broker.start().then(() => {
      broker.repl();
    })
    • Para conseguir fazer os microsserviços conversarem, precisaremos no NATS!
    • A forma mais fácil de trazer ele pro jogo é por meio do Docker:
    docker run --name nats -p 4222:4222 nats

    E pronto! Temos nossa solução em microsserviços! 😄

    💻 Repositório

    https://github.com/Professor-DiegoPinho/node-microservice-moleculer

    🌐 Referências e outros materiais

    Versão em vídeo

    Confira a versão em vídeo desse artigo!

  • sftpdav: Acesso Remoto via SSH com WebDAV

    sftpdav: Acesso Remoto via SSH com WebDAV

    Recentemente, enfrentei um desafio ao tentar acessar arquivos de uma máquina que só permite conexão via SSH. Abrir portas adicionais ou instalar extensões de terceiros não eram opções viáveis por questões de segurança e compatibilidade, especialmente no macOS.

    Foi assim que surgiu a ideia de criar o sftpdav, um programa que combina um cliente SFTP com um servidor WebDAV, permitindo acessar um diretório remoto via SSH e montá-lo localmente como se fosse um compartilhamento WebDAV.

    Na prática, estou usando o rsync e mantendo os arquivos sincronizados entre a máquina local e o servidor remoto. O sftpdav é uma solução interessante, mas o desempenho não é tão bom. Ainda assim, foi muito proveitoso desenvolver essa solução e aprender mais sobre o protocolo WebDAV e SSH.

    O código-fonte do sftpdav está disponível no GitHub: sftpdav.

    Requisitos

    A necessidade surgiu porque:

    • Ambiente Restrito: O servidor só permite conexão via SSH.
    • Segurança: Não posso expor outras portas no servidor.
    • Compatibilidade: O sshfs não tem suporte nativo no macOS, e não desejo instalar softwares adicionais.
    • Lock de Arquivos: O servidor WebDAV deve suportar bloqueio de arquivos (lock), necessário para meu caso de uso (o NFS também possui, mas não é tão simples de configurar).
    • user-space: A solução deve ser executada em user-space, sem necessidade de privilégios de administrador.

    Com o sftpdav, é possível aproveitar a robustez do SSH e, ao mesmo tempo, oferecer a flexibilidade do WebDAV, que pode ser montado em sistemas Linux e macOS com comandos simples e em user-space.

    Como Funciona o sftpdav

    O programa realiza os seguintes passos:

    1. Leitura da Configuração SSH: Utiliza o arquivo ~/.ssh/config para obter informações como usuário, hostname, porta e arquivo de identidade.
    2. Conexão SFTP: Estabelece uma conexão SSH e, a partir dela, cria um cliente SFTP para acesso ao diretório remoto.
    3. Servidor WebDAV Local: Inicializa um servidor WebDAV com a biblioteca padrão do Go, expondo o diretório remoto via SFTP para ser montado localmente.
    4. Montagem do Compartilhamento: Com o servidor WebDAV em execução, o compartilhamento pode ser montado usando os comandos nativos do sistema.

    Exemplos Práticos

    Executando o sftpdav

    Para iniciar o servidor WebDAV com acesso SFTP, execute:

    ./sftpdav -port 8811 -host sshserver

    Parâmetros suportados:

    • -host: Nome do host conforme configurado no ~/.ssh/config.
    • -port: Porta local para o servidor WebDAV (padrão: 8811).
    • -remoteDir: Diretório remoto a ser exposto (padrão: ".").

    Montando o Compartilhamento

    No Linux

    Use o comando mount com o tipo davfs:

    sudo mount -t davfs http://localhost:8811 /mnt/sftp

    No macOS

    Use o comando mount_webdav:

    sudo mount_webdav http://localhost:8811 /mnt/sftp

    Para Desmontar

    Em ambos os sistemas, para desmontar o compartilhamento, execute:

    sudo umount /mnt/sftp
    

    Limpando Arquivos de Dados Estendidos (macOS)

    Como o WebDAV não suporta dados estendidos no macOS, podem ser criados arquivos iniciados por ._. Para removê-los, use:

    dot_clean /caminho/do/diretorio
    

    A Versão Original

    A versão original era mais simples, consistindo em poucas linhas de código para criar um servidor WebDAV. No entanto, ela precisava ser executada no servidor remoto:

    package main
    
    import (
        "flag"
        "log"
        "net/http"
        "time"
    
        "golang.org/x/net/webdav"
    )
    
    func main() {
        localPortFlag := flag.String("port", "8811", "Local port for WebDAV server")
        dirFlag := flag.String("dir", "./", "Directory to share")
        flag.Parse()
    
        handler := &webdav.Handler{
            Prefix:     "/",
            FileSystem: webdav.Dir(*dirFlag),
            LockSystem: webdav.NewMemLS(),
        }
    
        s := &http.Server{
            Handler:        handler,
            Addr:           ":" + *localPortFlag,
            ReadTimeout:    15 * time.Second,
            WriteTimeout:   15 * time.Second,
            MaxHeaderBytes: 1 << 20,
        }
    
        if err := s.ListenAndServe(); err != nil {
            log.Fatal(err)
        }
    }

    Então, criava-se um túnel SSH para acessar o servidor remoto e montar o compartilhamento WebDAV localmente.

    Criando um Túnel SSH

    ssh -L 8811:localhost:8811 sshserver
    

    Neste exemplo, a porta 8811 da máquina local é redirecionada para a porta 8811 do servidor remoto, permitindo acesso local ao serviço WebDAV. Essa abordagem simples funcionava bem e é mais rápida que a versão atual.

    Considerações Finais

    sftpdav é uma solução experimental, mas funcional. Apesar de apresentar alguma lentidão, cumpre o objetivo de fornecer acesso remoto via WebDAV usando apenas a porta SSH, mantendo a segurança e a simplicidade do ambiente.

    A experiência com este projeto reforçou como é possível combinar diferentes protocolos para superar limitações e criar soluções interessantes e seguras.

    Caso tenha dúvidas ou sugestões, fique à vontade para entrar em contato ou abrir uma issue.

    Cesar Gimenes

     

  • Gestão de Projetos em 2030: como eu vejo o futuro

    Gestão de Projetos em 2030: como eu vejo o futuro

    Tenho refletido bastante sobre para onde está indo a gestão de projetos. Talvez eu erre em alguns pontos e tudo bem, o futuro tem dessas surpresas, mas acredito que alguns sinais já estão visíveis.

    Nos últimos anos, o próprio desenvolvimento de software vem mudando de forma acelerada: ciclos mais curtos, plataformas low-code/no-code, IA generativa ajudando em código, produtos sendo ajustados em tempo real com base em dados de uso. Isso, naturalmente, muda o terreno onde o gerente de projetos sempre atuou.

    Não é mais (ou cada vez menos será) aquele modelo sequencial de “grandes escopos fechados, longos cronogramas e checkpoints mensais”. Mesmo projetos grandes começam a ser quebrados em entregas menores, adaptáveis e com times altamente autônomos.

    E o que acontece com a profissão?

    Minha visão é que a profissão de gerente de projetos não vai desaparecer, mas está mudando de pele. Teremos espaço para menos gerentes que “controlam tarefas” e mais profissionais que orquestram, facilitam, integram visões e criam alinhamento entre múltiplas áreas, parceiros e tecnologias.

    A quantidade de oportunidades? Pode ser que diminua para quem atuar de forma tradicional, mas aumente para quem se adaptar. Alguns números globais mostram crescimento ainda sólido da profissão (PMI estima milhões de novas vagas), mas com novas exigências de perfil.

    Como eu me prepararia hoje (e estou tentando fazer isso)

    • Dominar o básico, mas ir além da certificação
      Sim, PMP, PMBOK, Agile, Scrum, tudo isso ainda conta. Mas é só o chão firme. Cada vez mais, o gerente de projetos precisará também entender de produto, de negócios, de tecnologia (sem ser dev, mas falando bem com devs), de finanças e de métricas de impacto.
    • Focar nas habilidades humanas
      Negociação, comunicação, alinhamento de interesses, gestão de expectativas, conversas difíceis. Soft skills são cada vez mais o diferencial.
    • Entender tecnologia como parceiro, não ameaça
      A IA vai ajudar a planejar, prever riscos, alocar recursos. Quem souber trabalhar bem com essas ferramentas terá vantagem competitiva.
    • Pensar ESG desde o início
      Projetos já nascem com compromissos ambientais, sociais e éticos. Entender frameworks de sustentabilidade passa a ser obrigatório.
    • Ser global (mesmo no bairro)
      Mesmo atuando no Brasil, as interações multiculturais aumentam. Falar outros idiomas, entender outras culturas de trabalho e saber lidar com equipes distribuídas já é quase pré-requisito.

     Outro dia mesmo, eu estava em uma call com três fusos horários diferentes, percebi que metade da minha função ali não era falar de escopo, mas ajustar expectativas culturais, interpretar silêncios e, claro, garantir que todos estavam mesmo falando da mesma coisa. Esse tipo de habilidade não está em nenhum cronograma mas muda o projeto inteiro.

    O futuro da gestão de projetos não é fim da profissão. É fim de um jeito antigo de fazer, mas abertura para um papel ainda mais estratégico e relevante para quem souber se atualizar.

    Essa é, ao menos, a minha leitura de agora (e posso errar muito, voltarei aqui em 20230 para ler). E, no mais, sigo estudando, porque o futuro já começou.♦


    LEIA TAMBÉM

  • ASP .NET Core – Apresentando Fast Endpoints

    ASP .NET Core – Apresentando Fast Endpoints

    Neste artigo vou apresentar a biblioteca FastEndpoints, um framework minimalista usado para criar Web Apis na plataforma .NET

    FastEndpoints é uma biblioteca que tem a proposta de simplificar a criação de endpoints de Web API no .NET, utilizando uma abordagem baseada em classes e métodos declarativos. Ele elimina a necessidade de configurar controladores (Controllers) e roteamentos de maneira tradicional, tornando o desenvolvimento mais rápido e com menos código.

    .NET

    A criação de APIs da Web com o ASP.NET Core pode envolver muito código repetitivo, especialmente ao lidar com controladores, roteamento e vinculação de modelos. O FastEndpoints é uma biblioteca leve que simplifica esse processo, permitindo que você defina endpoints com o mínimo de código e ótimo desempenho.

    Ela segue o padrão REPR – Request-Endpoint-Response –  e oferece os seguintes vantagens :

    Menos código boilerplate: 

    Não há necessidade de criar controllers, actions e configurar roteamentos manualmente.

    Desempenho otimizado:

    Menor sobrecarga em comparação ao MVC padrão da ASP.NET Core.

    Desenvolvimento mais rápido:

    Criação de endpoints de maneira declarativa, com validações e injeção de dependência simplificados.

    Validações integradas:

    Suporte embutido para validação de modelos usando FluentValidation.

    OpenAPI/Swagger:

    Suporte nativo para gerar documentação da API

    Usando FastEndpoints

    Para usar o FastEndpoints basta criar um projeto ASP.NET Core e incluir o pacote FastEndpoints: dotnet add package FastEndpoints

    FastEndpoints adota uma abordagem Handler-Based (baseada em manipuladores) em vez de usar controladores convencionais do ASP.NET Core MVC, para simplificar a criação de APIs REST.

    O FastEndpoints utiliza a middleware pipeline do ASP.NET Core e o recurso de routing endpoint introduzido no .NET Core 3.0. Em vez de registrar rotas em controladores, ele registra diretamente as classes que implementam a lógica dos endpoints.

    Cada classe que herda de Endpoint<TRequest, TResponse> ou Endpoint<TRequest> é automaticamente registrada como um endpoint na aplicação.

    Durante a inicialização, o FastEndpoints escaneia o assembly e registra dinamicamente as classes que implementam a interface de endpoint.

    Recursos utilizados pelo FastEndpoints:

    Middleware Pipeline:  

    O FastEndpoints registra os endpoints na pipeline HTTP usando app.UseRouting() e app.UseEndpoints().

    Reflection (Reflexão)

    Ele usa reflection para encontrar todas as classes que herdam de Endpoint<> e registrá-las como endpoints HTTP.

    Minimal APIs (Introduzidas no .NET 6)

    Ele usa a API de rotas simplificada do Minimal APIs, mas oferece uma abordagem mais estruturada com base em classes.

    Assim, o FastEndoints não usa Controllers pois um controlador geralmente lida com várias ações (GET, POST, PUT, DELETE), o que pode dificultar a organização e manutenção do código.

    Além disso, Controladores dependem de atributos para mapear rotas, enquanto o FastEndpoints elimina essa necessidade, permitindo declarar rotas diretamente nas classes de endpoint.

    Desta forma ele usa classes separadas em vez de controladores pois cada classe de endpoint tem uma única responsabilidade (por exemplo, CreateProdutoEndpoint só lida com a criação de produtos). Com classes separadas, o código fica mais limpo e fácil de manter. Não há necessidade de lidar com controladores inchados.

    As rotas são definidas diretamente dentro dos endpoints, tornando o mapeamento mais explícito e flexível e ainda possui suporte integrado para validações, resposta de erros padronizada e filtros de autenticação/autorização.

    Tipos de endpoint no FastEndpoints

    O FastEndpoints oferece 4 tipos de endpoints base, que você pode herdar de:

    • Endpoint<TRequest>

    Use este tipo se houver apenas um DTO de solicitação. No entanto, você pode enviar qualquer objeto ao cliente que possa ser serializado como uma resposta com essa sobrecarga genérica.

    Endpoint<TRequest,TResponse>

    Use este tipo se você tiver DTOs de solicitação e resposta. O benefício dessa sobrecarga genérica é que você obtém acesso fortemente tipado às propriedades do DTO ao fazer testes de integração e validações.

    EndpointWithoutRequest

    Use este tipo se não houver DTO de solicitação nem resposta. Você também pode enviar qualquer objeto serializável como resposta aqui.

    EndpointWithoutRequest<TResponse>

    Use este tipo se não houver DTO de solicitação, mas houver um DTO de resposta.

    Também é possível definir endpoints com EmptyRequest e EmptyResponse, se necessário:

    public classEndpoint : Endpoint<EmptyRequest,EmptyResponse> { }

    Exemplo prático usando FastEndpoints

    Neste nosso primeiro contato com o FastEndpoints vejamos como criar uma Web API para realizar o CRUD básico em Produto.

    Crie um novo projeto no VS 2022 usando o template ASP.NET Core Web API com o nome ApiFastEndpointsCrud sem usar Controllers. Neste exemplo vou usar um banco de dados  SQL Server.

    A seguir inclua no projeto os seguintes pacotse nuget :   

    dotnet add package FastEndpoints
    dotnet add package Microsoft.EntityFrameworkCore.SqlServer
    dotnet add package Microsoft.EntityFrameworkCore.Tools

    Agora configure o FastEndpoints na classe Program:

    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddOpenApi();
    
    // Registrando o FastEndpoints 
    builder.Services.AddFastEndpoints();
    var app = builder.Build();
    // Ativando FastEndpoints
    app.UseFastEndpoints();
    ...

    Crie a pasta Entities no projeto e nesta pasta cria a classe Produto:

    public class Produto
    {
        public int Id { get; set; }
        public string Nome { get; set; } = string.Empty;
        public decimal Preco { get; set; }
        public int Estoque { get; set; }
    }

    Vamos criar uma pasta chamada Context no projeto e incluir nesta pasta a classe AppDbContext que herda de DbContext:

    
    ppublic class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
    
       public DbSet<Produto> Produtos { get; set; }
    }

    Agora adicione na classe Program o código para registrar o serviço do contexto definindo o provedor do banco e obtendo a string de conexão:

    ...
    builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
    ...

    A seguir defina no arquivo appsettings.json a string de conexão usada para acessar o SQL Sever local:

    
    ...
     "ConnectionStrings": {
    "DefaultConnection": "Server=Macoratti\\SqlExpress;Database=FastEndpointsDB;Trusted_Connection=True;MultipleActiveResultSets=true;TrustServerCertificate=true;"
    },
    ...

    Criando os endpoints

    Agora vamos criar as classes para cada Endpoint. Para isso vamos criar a pasta Produtos no projeto e nesta pasta vamos criar as seguintes classes:

    📄 CriarProdutoEndpoint.cs – Para criar um produto (POST).
    📄 CriarProdutoRequest.cs – DTO usado para fornecer os dados de entrada do produto
    📄 ObterProdutosEndpoint.cs – Para listar todos os produtos (GET).
    📄 ObterProdutoEndpoint.cs – Para obter um produto pelo seu id (GET).
    📄 AtualizarProdutoEndpoint.cs – Para atualizar um produto  (PUT).
    📄 DeletarProdutoEndpoint.cs – Para deletar um produto por ID (DELETE).
    📄 AtualizarProdutoRequest.cs – DTO usado para fornecer os dados de entrada.
    📄 ObterProdutoIdRequest.cs – DTO usado para fornecer o Id do Produto

    Essa estrutura mantém o código separado e organizado, facilitando a manutenção. Aseguir temos o código de cada classew

    1- Listar todos os produtos

    public class ObterProdutosEndpoint : EndpointWithoutRequest<List<Produto>> 
    {
        private readonly AppDbContext _context;
        public ObterProdutosEndpoint(AppDbContext context)
        {
            _context = context;
        }
        public override void Configure()
        {
            Verbs(Http.GET);
            Routes("/produtos");
            Get("/produtos");
            AllowAnonymous();
        }
        public override async Task HandleAsync(CancellationToken ct)
        {
            var produtos = await _context.Produtos.ToListAsync(ct); 
            if (produtos.IsNullOrEmpty())
            {
               // Retorna 404 se não houver produtos.
                await SendNotFoundAsync();
                return;
            }
            await SendOkAsync(produtos); // Retorna 200 OK com a lista de produtos
            // Ou, para melhor performance em alguns casos (dependendo do tamanho da lista):
            // await SendAsync(produtos); // Envia diretamente a lista, 
            // sem serialização extra (pode precisar de configuração no Program.cs)
        }
    }

    Vamos entender o código :

    A classe herda de EndpointWithoutRequest<List<Produto>>, o que significa que:

    EndpointWithoutRequest → Indica que não recebe nenhum dado na requisição (Body).
    List<Produto> → O endpoint retorna uma lista de produtos.

    contexto é injetado via construtor (injeção de dependência), permitindo que o endpoint acesse os métodos para manipular dados.

    O método Configure configura as propriedades do endopint:

    Método Descrição
    Verbs() Define o verbo HTTP usado pelo endpoint (GET).
    Routes() Define a rota do endpoint (/produtos).
    AllowAnonymous Permite que o endpoint seja acessado sem autenticação.

    O método HandleAsync é o método principal do endpoint, que será chamado quando uma requisição for feita para /produtos.

    __context.Produtos.ToListAsync(ct)→ Chama o método para buscar todos os produtos da base de dados.

    SendOkAsync(produtos) → Envia a resposta ao cliente com a lista de produtos (status HTTP 200 OK).

    Se o produto não for encontrado usando o método SendNotFoundAsync() para enviar um status code 404.

    2- Criar um novo produto

    public class CriarProdutoEndpoint : Endpoint<CriarProdutoRequest, Produto> 
    {
        private readonly AppDbContext _context;
        public CriarProdutoEndpoint(AppDbContext context)
        {
            _context = context;
        }
        public override void Configure()
        {
            Post("/produtos");
            AllowAnonymous();
        }
        public override async Task 
                              HandleAsync(CriarProdutoRequest req,
                              CancellationToken ct)
        {
            var produto = new Produto
            {
                Nome = req.Nome,
                Preco = req.Preco,
                Estoque = req.Estoque
            };
            _context.Produtos.Add(produto);
            await _context.SaveChangesAsync(ct);
            await SendCreatedAtAsync<CriarProdutoEndpoint>($"/produtos/{produto.Id}", 
                                                                       produto);
        }
    }

    Esta classe usa o DTO CriarProdutoRequest para fornecer os dados do produto que será criado:

    public class CriarProdutoRequest
    {
        public string Nome { get; set; } = string.Empty;
        public decimal Preco { get; set; }
        public int Estoque { get; set; }
    }

    3- Lista um produto pelo seu id

    public class ObterProdutoEndpoint : Endpoint<ObterProdutoIdRequest> 
    {
        private readonly AppDbContext _context;
        public ObterProdutoEndpoint(AppDbContext context)
        {
            _context = context;
        }
        public override void Configure()
        {
            // {Id} deve corresponder à propriedade do DTO (case-sensitive)
            Get("/produtos/{Id}"); 
            AllowAnonymous();
        }
        public override async Task 
                              HandleAsync(ObterProdutoIdRequest req,
                              CancellationToken ct)
        {
            // Acessa o ID através do DTO
            var produto = await _context.Produtos.FindAsync(req.Id, ct); 
            if (produto == null)
            {
                await SendNotFoundAsync();
                return;
            }
            await SendOkAsync(produto);
        }
    }

    Esta classe usa o DTO ObterProdutoIdRequest para fornecer id do produto  que será obtido.

    public class ObterProdutoIdRequest
    {
        public int Id { get; set; }
    }

    4- Atualiza um produto

    public class AtualizarProdutoEndpoint : Endpoint<AtualizarProdutoRequest>
    {
        private readonly AppDbContext _context;
        public AtualizarProdutoEndpoint(AppDbContext context)
        {
            _context = context;
        }
        public override void Configure()
        {
            Put("/produtos/{id}");
            AllowAnonymous();
        }
        public override async Task HandleAsync(AtualizarProdutoRequest req, 
                                                      CancellationToken ct)
        {
            var produto = await _context.Produtos.FindAsync(req.Id, ct);
            if (produto == null)
            {
                await SendNotFoundAsync();
                return;
            }
            produto.Nome = req.Nome;
            produto.Preco = req.Preco;
            produto.Estoque = req.Estoque;
            await _context.SaveChangesAsync(ct);
            await SendOkAsync(produto);
        }
    }

    Esta classe usa o DTO AtualizarProdutoIdRequest para fornecer o id do produto  que será atualizado e os dados do produto que são herdados de CriarProdutoRequest:

    public class AtualizarProdutoRequest : CriarProdutoRequest 
    {
         public int Id { get; set; }
    }

    5- Excluir um produto

    public class DeletarProdutoEndpoint : Endpoint<ObterProdutoIdRequest>
    {
        private readonly AppDbContext _context;
        public DeletarProdutoEndpoint(AppDbContext context)
        {
            _context = context;
        }
        public override void Configure()
        {
            Delete("/produtos/{id}");
            AllowAnonymous();
        }
        public override async Task HandleAsync(ObterProdutoIdRequest req,
                                       CancellationToken ct)
        {
            var produto = await _context.Produtos.FindAsync(req.Id, ct);
            if (produto == null)
            {
                await SendNotFoundAsync();
                return;
            }
            _context.Produtos.Remove(produto);
            await _context.SaveChangesAsync(ct);
            await SendNoContentAsync(); // Retorna 204 No Content
        }
    }

    Esta classe também usa o DTO ObterProdutoIdRequest para fornecer id do produto  que será excluído.

    public class ObterProdutoIdRequest
    {
        public int Id { get; set; }
    }

    Com isso temos os endpoints definidos e eles serão exibidos na interface do Swagger da seguinte forma:

    Para realizar os testes nestes endpoints de forma mais fácil podemos usar o Postman.

    Pegue o projeto aqui:  ApiFastEndpointsCrud.zip

    E estamos conversados… 

  • Dica de ouro: Saiba como fazer uma navbar responsiva

    Dica de ouro: Saiba como fazer uma navbar responsiva

    Hoje é praticamente impossível ter um site que não seja responsivo.

    Existem várias formas criativas e técnicas de lidar com responsividade (ex: Flexbox, Media Queries, CSS Grid, etc.) nos mais diferentes contextos, mas há algumas adaptações que são praticamente padrões em todos os projetos.

    Um claro exemplo é a navbar responsiva. Quando ela possui muitos itens, inevitavelmente se torna imprópria para o ambiente mobile, pois os itens literalmente não cabem no espaço disponível.

    Nessas situações, precisamos pensar em técnicas criativas. Hoje vamos aprender como fazer uma navbar responsiva minimalista e elegante usando apenas o básico HTML, CSS e um pouquinho de nada de JavaScript.

    Para aprendemos como fazer isso, tomaremos como exemplo a solução que apliquei no meu próprio site pessoal. Sou suspeito pra falar, mas acho que ela atendeu bem ao seu objetivo 😄

    funcionamento da navbar responsiva

    Vamos começar criando a estrutura básica do site para desktop. Neste caso, precisamos essencialmente de três elementos HTML: uma tag <nav> para a barra de navegação, uma tag <img> para o logotipo e tags <ul> e <li> para a lista de links:

    <!DOCTYPE html>
    <html lang="pt-br">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Menu Responsivo</title>
      <link rel="stylesheet" href="style.css">
    </head>
    
    <body>
      <nav class="navbar">
        <img src="logo.png" alt="logo">
        <div class="navbar-links">
          <ul>
            <li><a href="#">Home</a></li>
            <li><a href="#">Sobre</a></li>
            <li><a href="#">Contato</a></li>
          </ul>
        </div>
      </nav>
    </body>
    
    </html>

    Vamos aplicar o CSS básico para que ele funcione bem no desktop:

    @import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
    
    * {
      box-sizing: border-box;
    }
    
    body {
      font-family: "Poppins", sans-serif;
      margin: 0;
      padding: 0;
    }
    
    .navbar {
      display: flex;
      position: relative;
      justify-content: space-around;
      align-items: center;
      border-bottom-color: rgb(234, 236, 243);
      border-bottom-style: solid;
      border-bottom-width: 1px;
    }
    
    .navbar img {
      width: 220px;
    }
    
    .navbar-links {
      height: 100%;
    }
    
    .navbar-links ul {
      display: flex;
      margin: 0;
      padding: 0;
    }
    
    .navbar-links li {
      list-style: none;
    }
    
    .navbar-links li a {
      display: block;
      text-decoration: none;
      padding: 1rem;
      color: #333;
      font-weight: 500;
    }
    
    .navbar-links li:hover {
      background-color: #DBDBDB;
    }
    • Aqui surge nosso primeiro problema: à medida que a tela fica menor, nossa navbar começa a transbordar horizontalmente. Esse comportamento não é desejável. Portanto, precisamos implementar o menu “hambúrguer”.
    • O primeiro passo para isso é adicionar no HTML o botão que corresponderá ao menu e as opções que ele oferecerá. Há diferentes maneiras de criamos esse botão, por ora, faremos ela na “mão”:
    <!DOCTYPE html>
    <html lang="pt-br">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Menu Responsivo</title>
      <link rel="stylesheet" href="style.css">
    </head>
    
    <body>
      <nav class="navbar">
        <img src="logo.png" alt="logo">
        <a href="#" class="toggle-button">
          <span class="bar"></span>
          <span class="bar"></span>
          <span class="bar"></span>
        </a>
        <div class="navbar-links">
          <ul>
            <li><a href="#">Home</a></li>
            <li><a href="#">Sobre</a></li>
            <li><a href="#">Contato</a></li>
          </ul>
        </div>
      </nav>
    
      <script src="script.js"></script>
    </body>
    
    </html>

    Agora vamos adicionar o CSS que vai construí-lo adequadamente na tela:

    .toggle-button {
      position: absolute;
      top: .75rem;
      right: 1rem;
      display: none;
      flex-direction: column;
      justify-content: space-between;
      width: 30px;
      height: 21px;
    }
    
    .toggle-button .bar {
      height: 3px;
      width: 100%;
      background-color: #333;
      border-radius: 10px;
    }
    • Agora temos que ajustar para que ele apareça somente quando o dispositivo tiver a tela pequena. Para isso, precisaremos das Media Queries.
    • As media queries funcionam como condicionais dentro do nosso código CSS. Desta forma, conseguimos trabalhar com condições para que um elemento apareça ou não. Vamos ver como fica nosso exemplo:
    @media (max-width: 800px) {
      .navbar {
        flex-direction: column;
        align-items: center;
      }
    
      .toggle-button {
        display: flex;
      }
    
      .navbar-links {
        display: none;
        width: 100%;
      }
    
      .navbar-links ul {
        width: 100%;
        flex-direction: column;
      }
    
      .navbar-links ul li {
        text-align: center;
      }
    
      .navbar-links ul li a {
        padding: .5rem 1rem;
      }
    
    }

    Agora temos mais um problema para resolver. Ao clicar no menu, queremos que os itens apareçam e quando clicarmos novamente, que eles sumam. É neste momento que precisaremos da ajuda do JavaScript!

    const toggleButton = document.getElementsByClassName("toggle-button")[0];
    const navbarLinks = document.getElementsByClassName("navbar-links")[0];
    
    toggleButton.addEventListener("click", () => {
      navbarLinks.classList.toggle("active");
    });
    
    .navbar-links.active {
      display: flex;
    }

    E pronto! Temos nossa navbar responsiva lindona! Basta adaptar os textos, opções, tamanhos e afins para melhor se adequar ao seu projeto! 😀

    Repositório

    https://github.com/Professor-DiegoPinho/menu-responsivo

    Agradecimentos

    Gostou deste conteúdo? Então curta e compartilhe! 👏

  • Trabalhar com Azure ou AWS? Qual é a melhor?

    Trabalhar com Azure ou AWS? Qual é a melhor?

    Olá pessoal, hoje eu vou contar um pouco da experiência que passei ao trabalhar com duas ferramentas que fornecem o mesmo tipo de serviço, porém de maneiras diferentes. São duas plataformas que oferecem basicamente os mesmos serviços para desenvolvedores individuais e empresariais.

    É possível utilizar os dois serviços ao mesmo tempo e é de grande valia, um serviço pode compensar mais do que o outro, mas de antemão eu já digo que são duas grandes empresas que fornecem grandes serviços. Melhor do que essas pequenas empresas que oferecem hospedagem sem garantir backup ou suporte de alta qualidade.

    Azure ou AWS

    As duas empresas oferecem serviços de hospedagem de site, banco de dados e serviços móveis. Esses foram os serviços que utilizei no Azure da Microsoft e no AWS da Amazon. O banco de dados utilizado no Azure foi o SQL Server e no AWS foi o DynamoDB. Sei que são bancos de dados diferentes, mas eu fiquei realmente triste com o DynamoDB. Primeiro, eu preciso utilizar o framework da Amazon, isto é, tenho que baixar um pacote API, instalar no meu computador e, depois, chamar os métodos que a empresa disponibiliza para consumo. Alguns métodos bons e outros nem tanto, alguns ruins e outros nem existem.

    Por quê é ruim utilizar uma API de terceiros para ter acesso ao banco de dados? Porque você está na mão das pessoas que desenvolvem essa API. Se algum método for retirado, você vai ter que alterar em seu sistema. Imagina que você tem mais de 30 mil linhas de código, veja o trabalho de procurar e alterar tudo. Se a empresa não dá mais suporte a API que você utiliza, você tem que criar uma ou mudar para outra e isso envolve trabalho, envolve tempo e dinheiro.

    Falando na parte do Azure, eu não precisei de API para inserir, alterar, pesquisar e excluir dados no banco de dados. Basta o drive de conexão que todos os banco precisam ao invés de API qualquer. Tome cuidado com elas, pois empresas criam do dia para noite e acaba não abordando todos os casos que você precisa. Sempre haverá um caso não construído ou sem algum método e isso resulta em fazer “gambiarra” que não é o perfil de um ótimo desenvolvedor ou analista de sistemas. No caso de banco de dados, eu prefiro hospedar no Microsoft Azure. Existem outras ferramentas de dados dentro do Azure, mas eu não tive a oportunidade de utilizar ainda.

    Hospedagem

    Para a hospedagem de arquivo como imagem, arquivos pdf ou outras extensões, o Azure não possui esse serviço ainda. Você precisa criar uma máquina virtual ou coisa parecida, ao contrário do AWS que oferece o serviço S3 para colocar arquivos de qualquer extensão. O ruim é que você precisa utilizar a API deles para fazer upload dos arquivos via sistema, caso contrário é necessário utilizar o web site (não muito bom na minha opinião) para enviar arquivo, mas funciona razoavelmente. Na hospedagem do site, estou utilizando o Azure, mas para arquivos do site enviados e para o site. A imagem de usuário ou arquivos de artigos, utilizo o AWS S3. Funciona muito bem e nunca tive problema. É lógico que no banco de dados eu coloco apenas o nome do arquivo e no sistema coloco o endereço fixo do AWS S3 para que no caso de alguma mudança de serviço/endereço, eu mudo apenas no arquivo de configuração do sistema e tudo funciona sem muito trabalho.

     Falando de hospedagem de site, as duas empresas são ótimas e funcionam muito bem. No AWS você precisa contratar o serviço chamado Elastic Beanstalk onde é criado um ambiente virtual com Windows ou outro sistema operacional, para hospedar a tecnologia .NET (que foi o meu caso). Basta informar a configuração da máquina que deseja e será montada. Acesso às configurações, Windows Explorer do ambiente você não tem acesso. Não é tratado como uma máquina virtual, é só um ambiente e para enviar o site ou sistema, é necessário instalar o plugin deles na ferramenta Visual Studio da Microsoft. Esse plugin não funciona para algumas versões do Windows ou Visual Studio, eu só consegui instalar no Windows 10, no 8.1 não instalou. Na verdade falava que estava instalado mas não aparecia no Visual Studio e pesquisando na Internet, eu vi vários relatos, muitos deram o mesmo problema.

     Para hospedar no Azure é muito mais fácil, pois o Visual Studio já vem com a opção de enviar para o ambiente necessário. Você clica em um botão, digita o seu usuário e escolhe o endereço, em pouco tempo o site está no ar com endereço …azurewebsites.net. A configuração do site, memória e processamento pode ser feito pelo portal do Azure. Em questão de configuração, o Azure te dá opções para hospedar outras linguagens incluindo Java e no AWS você precisa criar outro ambiente por exemplo. Neste ponto, você paga dois endereços enquanto que o Azure você coloca tudo em um. A vantagem do AWS é que você pode ficar até 1 ano utilizando sem pagar, o Azure são alguns meses.

    Configuração

    Configuração de SSL que é o famoso HTTPS para os sites, o Azure oferece um layout pelo próprio portal, simples e fácil; enquanto que no AWS não oferece um “faça você mesmo”. Isso eu achei ruim no AWS porque é um serviço muito simples de adicionar sem precisar de suporte. Em alguns pontos, o Amazon “peca” muito ou não pensa no desenvolvedor.

     Eu utilizei o sistema para analisar dados e web service dos dois, mas eu não vou me prolongar para o artigo não ficar grande.

     Em resumo, eu vou continuar mesclando as duas tecnologias, pegando o melhor delas para desenvolver meus sistemas e softwares. O mais importante é deixar seu sistema funcionando com mais fluidez para o usuário utilizar. Espero que tenha gostado um pouco do que foi falado aqui e que sirva como experiência.

     Qualquer dúvida pode entrar em contato pelo site www.mauriciojunior.net.