<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Matheus Cruvinel</title>
    <description>The latest articles on Forem by Matheus Cruvinel (@mcruvinel).</description>
    <link>https://forem.com/mcruvinel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F946246%2F4d31355b-d0dc-4c9c-8c6c-0e90045d26d6.png</url>
      <title>Forem: Matheus Cruvinel</title>
      <link>https://forem.com/mcruvinel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mcruvinel"/>
    <language>en</language>
    <item>
      <title>Hacktoberfest 2025: A Journey Through Multiple Perspectives</title>
      <dc:creator>Matheus Cruvinel</dc:creator>
      <pubDate>Sat, 25 Oct 2025 03:08:33 +0000</pubDate>
      <link>https://forem.com/mcruvinel/hacktoberfest-2025-a-journey-through-multiple-perspectives-51l6</link>
      <guid>https://forem.com/mcruvinel/hacktoberfest-2025-a-journey-through-multiple-perspectives-51l6</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest2025"&gt;2025 Hacktoberfest Writing Challenge&lt;/a&gt;: Open Source Reflections&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Kaleidoscope of Open Source
&lt;/h2&gt;

&lt;p&gt;When I started my Hacktoberfest 2025 journey, I had a limited view of what contributing to open source would be. I imagined it would basically be writing code following established patterns. What I discovered was infinitely richer: each project is a universe with its own culture, philosophy, and way of seeing problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Living Library of Solutions
&lt;/h2&gt;

&lt;p&gt;The most fascinating aspect of diving into different open source projects was realizing that there's no "the" correct way to solve a problem—there are dozens of valid approaches, each with its own trade-offs and contexts.&lt;/p&gt;

&lt;p&gt;I contributed to a data analysis project where performance was priority number one. Every line of code was designed to save milliseconds. Weeks later, I worked on an educational library where clarity and readability were supreme, even if it cost a few CPU cycles. The "perfect" code in one context would be considered inadequate in the other.&lt;/p&gt;

&lt;p&gt;This diversity taught me something fundamental: software architecture isn't pure mathematics, it's contextual art.&lt;/p&gt;

&lt;h2&gt;
  
  
  Silent Conversations with Strangers
&lt;/h2&gt;

&lt;p&gt;One of the most surreal experiences was reading code from developers I've never met, but whose thinking I could decipher through their design choices. Every comment, every variable name, every data structure tells a story about how that person thinks.&lt;/p&gt;

&lt;p&gt;I saw projects where simplicity was elevated to an art form—small functions, descriptive names, zero unnecessary abstractions. I also saw projects with complex and elegant abstractions that solved problems I didn't even know existed.&lt;/p&gt;

&lt;p&gt;I learned that there are developers who think in layers, others in flows, some in states, others in transformations. And they're all correct within their contexts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Productive Culture Shock
&lt;/h2&gt;

&lt;p&gt;Each project has its own "personality." Some have 50-page guidelines detailing even comment spacing. Others have a three-line README saying "make a PR, we'll check it out later." Some use issues as living documentation, others prefer long discussions before any code.&lt;/p&gt;

&lt;p&gt;This diversity initially frustrated me. Why isn't there a universal standard? But then I understood: this apparent "mess" is actually a healthy ecosystem. Different projects attract different people, and that's wonderful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons I'll Carry Forward
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Technical Humility
&lt;/h3&gt;

&lt;p&gt;Every project showed me something I didn't know. Today's junior developer might be solving a problem you never could. Seniority lies in recognizing this.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Context is King
&lt;/h3&gt;

&lt;p&gt;The best design pattern, the most elegant architecture, the smartest optimization—all worth zero if they don't fit the project's context. I learned to ask "why?" before suggesting "how."&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Documentation is Love
&lt;/h3&gt;

&lt;p&gt;Poorly documented code is disguised selfishness. I discovered this trying to contribute to projects without adequate context. Now, when I write, I think about the person who will read it—and this changed how I code.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Community Matters More Than Code
&lt;/h3&gt;

&lt;p&gt;Projects don't die from lack of features, but from lack of community. The most vibrant projects I found had maintainers who responded with patience, celebrated small contributions, and created safe spaces for beginners to make mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Tell Someone Starting Out
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Don't seek the perfect project, seek the different project.&lt;/strong&gt; Each contribution is a window into a new way of thinking. The more diverse your contribution portfolio, the more versatile you become.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accept that you'll feel lost.&lt;/strong&gt; It's not a sign of incompetence, it's a sign that you're growing. The best developers I've met in open source are those who admit they don't know something three times a day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read more code than you write.&lt;/strong&gt; At first, I spent 80% of my time reading and 20% writing. It was the best investment I made. Each codebase is a book about design decisions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ask "dumb" questions.&lt;/strong&gt; That doubt you think is obvious might be hidden in poor documentation. Your questions help the next person.&lt;/p&gt;

&lt;h2&gt;
  
  
  The True Value of Open Source
&lt;/h2&gt;

&lt;p&gt;In the end, I realized that open source isn't about code—it's about perspectives. It's a place where you can peek at how people on the other side of the world solve problems, how they think, how they prioritize.&lt;/p&gt;

&lt;p&gt;It's a space where a developer in Brazil can learn techniques from someone in Japan, where a student in India can improve a tool used by a company in Germany. It's a global, asynchronous, and silent conversation, but deeply human.&lt;/p&gt;

&lt;p&gt;Hacktoberfest 2025 gave me more than lines on my GitHub profile. It gave me humility, curiosity, and a deep appreciation for the diversity of thought that exists in our field.&lt;/p&gt;

&lt;p&gt;And that's priceless.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Did you participate in Hacktoberfest? What was your biggest discovery? Share in the comments!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hacktoberfest</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Como construí um Sistema de Controle Financeiro usando APENAS S3 (sem PostgreSQL!)</title>
      <dc:creator>Matheus Cruvinel</dc:creator>
      <pubDate>Wed, 22 Oct 2025 22:03:15 +0000</pubDate>
      <link>https://forem.com/magalucloud/como-construi-um-sistema-de-controle-financeiro-usando-apenas-s3-sem-postgresql-2iec</link>
      <guid>https://forem.com/magalucloud/como-construi-um-sistema-de-controle-financeiro-usando-apenas-s3-sem-postgresql-2iec</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Construí um sistema completo de gestão financeira pessoal usando apenas Object Storage (S3). Sem PostgreSQL, sem MongoDB, sem Redis. Resultado: infraestrutura de ~R$1/mês com performance surpreendente.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Provocação
&lt;/h2&gt;

&lt;p&gt;Durante uma conversa com alguns amigos, surgiu a pergunta: &lt;em&gt;"Você realmente precisa de um banco de dados para tudo?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A resposta padrão seria: &lt;em&gt;"Claro! Como você vai fazer queries? E as transações? E os índices?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Mas e se... não precisássemos?&lt;/p&gt;

&lt;p&gt;E se pudéssemos construir um sistema funcional de controle financeiro usando &lt;strong&gt;apenas Object Storage&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;Spoiler: Funcionou. E funcionou bem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffmny54m0bw68dsq1y92j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffmny54m0bw68dsq1y92j.png" alt="Dashboard NimbusVault" width="800" height="611"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  O Problema Real
&lt;/h2&gt;

&lt;p&gt;Como desenvolvedor, gerenciar finanças pessoais sempre foi caótico para mim:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📂 Faturas de cartão espalhadas no email&lt;/li&gt;
&lt;li&gt;🧾 Comprovantes em pastas desorganizadas no Drive&lt;/li&gt;
&lt;li&gt;💼 Recibos de investimentos perdidos&lt;/li&gt;
&lt;li&gt;📊 Documentos do IR em diversos lugares&lt;/li&gt;
&lt;li&gt;🔍 Impossível encontrar algo quando precisa&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pior: tentei usar apps prontos, mas todos tinham problemas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Apps mobile&lt;/strong&gt;: Ótimos para registrar gastos, péssimos para documentos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Drive&lt;/strong&gt;: Falta estrutura e automação&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Planilhas&lt;/strong&gt;: Funcionam até você ter 500 linhas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apps de terceiros&lt;/strong&gt;: Lock-in + custos recorrentes altos&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eu queria algo &lt;strong&gt;simples, barato e que EU controlasse&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy: 1 Minuto
&lt;/h2&gt;

&lt;p&gt;Usei Docker Compose para tornar o deploy trivial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./backend&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8000:8000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BUCKET=${BUCKET}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ACCESS_KEY=${ACCESS_KEY}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SECRET_KEY=${SECRET_KEY}&lt;/span&gt;

  &lt;span class="na"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./frontend&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:80"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mcruvinel/NimbusVault
&lt;span class="nb"&gt;cd &lt;/span&gt;NimbusVault
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env  &lt;span class="c"&gt;# Configure suas credenciais&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pronto. Sistema rodando.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Ideia Maluca
&lt;/h2&gt;

&lt;p&gt;"E se eu usar S3 como 'banco de dados'?"&lt;/p&gt;

&lt;p&gt;Meu primeiro pensamento foi: &lt;em&gt;"Isso é burrice, S3 não foi feito pra isso"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Mas então percebi algumas coisas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;S3 tem estrutura hierárquica&lt;/strong&gt; (&lt;code&gt;categoria/arquivo.pdf&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;list_objects_v2 é como um SELECT&lt;/strong&gt; com filtros&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;URLs pré-assinadas eliminam proxy de arquivos&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durabilidade de 99.999999999%&lt;/strong&gt; (melhor que meu PostgreSQL caseiro)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custo ridiculamente baixo&lt;/strong&gt; para uso pessoal&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Decidi testar. O pior que poderia acontecer era perder um fim de semana.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Solução: NimbusVault
&lt;/h2&gt;

&lt;p&gt;Construí o &lt;strong&gt;NimbusVault&lt;/strong&gt; em um fim de semana. O conceito é simples:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Object Storage organizado = Banco de dados funcional&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Arquitetura
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────┐      ┌──────────────┐      ┌─────────────────┐
│   React     │─────▶│   FastAPI    │─────▶│  MagaluCloud    │
│  Frontend   │      │   Backend    │      │  Object Storage │
└─────────────┘      └──────────────┘      └─────────────────┘
  Tailwind UI          boto3 + S3            11 nines durability
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Estrutura "Tabelas"
&lt;/h3&gt;

&lt;p&gt;Em vez de tabelas SQL, uso &lt;strong&gt;prefixos como categorias&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;finance-bucket/
├── despesas-mensais/
│   ├── fatura-cartao-out-2025.pdf
│   ├── recibo-aluguel-out.pdf
│   └── recibo-energia-outubro.pdf
├── receitas/
│   ├── holerite-outubro-2025.pdf
│   └── freelance-projeto-x.pdf
├── investimentos/
│   ├── tesouro-direto-setembro.pdf
│   └── extrato-corretora-out.pdf
└── impostos/
    ├── darf-irpf-2024.pdf
    └── comprovante-iptu.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simples. Óbvio. &lt;strong&gt;Funcional&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementação
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Backend Minimalista (FastAPI)
&lt;/h3&gt;

&lt;p&gt;O backend tem &lt;strong&gt;menos de 200 linhas&lt;/strong&gt;. Não é exagero.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UploadFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NimbusVault&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;endpoint_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ENDPOINT_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aws_access_key_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ACCESS_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aws_secret_access_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/upload&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upload_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;(...),&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UploadFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(...)):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Upload de documento financeiro&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;BUCKET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ContentType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_files&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Lista documentos (opcionalmente filtrados por categoria)&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bucket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BUCKET&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Prefix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_objects_v2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Contents&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
        &lt;span class="c1"&gt;# Gera URL pré-assinada (válida por 1 hora)
&lt;/span&gt;        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_presigned_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;get_object&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Bucket&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BUCKET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
            &lt;span class="n"&gt;ExpiresIn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;modified&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LastModified&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;files&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso é tudo. Sério.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend Responsivo (React + Tailwind)
&lt;/h3&gt;

&lt;p&gt;Interface clean com drag-and-drop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;NimbusVault&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCategory&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;despesas-mensais&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFiles&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleUpload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;category&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/upload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;loadFiles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CategorySelector&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;setCategory&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UploadZone&lt;/span&gt; &lt;span class="na"&gt;onUpload&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleUpload&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FileList&lt;/span&gt; &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Features Surpreendentes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. URLs Pré-assinadas (Game Changer)
&lt;/h3&gt;

&lt;p&gt;Em vez de fazer o backend servir arquivos (gastando banda e CPU), gero &lt;strong&gt;URLs temporárias que apontam direto pro S3&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;presigned_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_presigned_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;get_object&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Bucket&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BUCKET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;receitas/holerite.pdf&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;ExpiresIn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;  &lt;span class="c1"&gt;# 1 hora
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vantagens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Download direto do S3 (super rápido)&lt;/li&gt;
&lt;li&gt;✅ Backend não processa tráfego&lt;/li&gt;
&lt;li&gt;✅ Segurança: URLs expiram automaticamente&lt;/li&gt;
&lt;li&gt;✅ Funciona com &lt;code&gt;&amp;lt;a download&amp;gt;&lt;/code&gt; nativamente&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Busca "Good Enough"
&lt;/h3&gt;

&lt;p&gt;Não tenho Elasticsearch, mas funciona:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Backend lista TODOS os arquivos (rápido até ~10k objetos)
&lt;/span&gt;&lt;span class="n"&gt;all_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_objects_v2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;BUCKET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Frontend filtra com JavaScript
&lt;/span&gt;&lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="n"&gt;filteredFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
  &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searchQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para 99% dos casos de uso pessoal (&amp;lt; 5000 documentos), &lt;strong&gt;isso é mais que suficiente&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. "Particionamento" Natural
&lt;/h3&gt;

&lt;p&gt;Usar prefixos como categorias é essencialmente particionamento:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Lista APENAS despesas (rápido)
&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_objects_v2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;BUCKET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;despesas-mensais/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Lista APENAS investimentos (rápido)
&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_objects_v2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;BUCKET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;investimentos/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mesmo com 10.000 arquivos, listar uma categoria específica é &lt;strong&gt;instantâneo&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Versionamento Grátis
&lt;/h3&gt;

&lt;p&gt;S3 tem versionamento nativo. Ativei e agora tenho &lt;strong&gt;audit trail de graça&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api list-object-versions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; finance-bucket &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--prefix&lt;/span&gt; despesas-mensais/fatura.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alguém deletou um documento importante? &lt;code&gt;s3.get_object(VersionId=...)&lt;/code&gt; e recuperei.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que MagaluCloud?
&lt;/h2&gt;

&lt;p&gt;Testei AWS S3, mas os custos no Brasil são altos (transferência de dados internacional).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MagaluCloud foi uma revelação:&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Vantagens
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Datacenters no Brasil&lt;/strong&gt; → latência &amp;lt;50ms (vs 150ms+ AWS EUA)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preços acessíveis&lt;/strong&gt; → R$0,06/GiB/mês de storage (Cold Instant)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API S3-compatível&lt;/strong&gt; → boto3 funciona sem alteração&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transferência barata&lt;/strong&gt; → R$0,20/GiB de saída, R$0,01/GiB de entrada&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suporte em português&lt;/strong&gt; → respondem em minutos&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Custos Reais (3 meses de uso)
&lt;/h3&gt;

&lt;p&gt;Meu uso pessoal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;5 GB de documentos&lt;/strong&gt; (≈4,66 GiB)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;~1000 requisições/mês&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Fatura mensal: ≈R$0,50/mês&lt;/strong&gt; 🤯&lt;/p&gt;

&lt;p&gt;Compare com:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostgreSQL gerenciado: R$50-200/mês&lt;/li&gt;
&lt;li&gt;MongoDB Atlas: R$80+/mês&lt;/li&gt;
&lt;li&gt;Heroku Postgres: $50/mês (~R$250)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quando Usar
&lt;/h2&gt;

&lt;p&gt;✅ &lt;strong&gt;Storage de documentos/comprovantes/arquivos&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Logs e eventos&lt;/strong&gt; (time-series)&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Prototipagem rápida&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Side projects&lt;/strong&gt; (custos baixos)&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Sistemas WORM&lt;/strong&gt; (Write Once, Read Many)&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Dados não-relacionais&lt;/strong&gt; (não precisa de joins)&lt;/p&gt;

&lt;p&gt;Basicamente: se você está pensando em "salvar arquivos no servidor", &lt;strong&gt;considere S3 diretamente&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Lições Aprendidas
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Simplicidade Vence
&lt;/h3&gt;

&lt;p&gt;Gastei &lt;strong&gt;5 horas&lt;/strong&gt; construindo isso. Gastaria &lt;strong&gt;5 semanas&lt;/strong&gt; com PostgreSQL + migrations + ORM + backups + monitoramento.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Object Storage é Subestimado
&lt;/h3&gt;

&lt;p&gt;Desenvolvedores pensam "S3 = arquivos grandes". Errado. S3 é &lt;strong&gt;key-value store distribuído&lt;/strong&gt; com storage barato.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Nem Tudo Precisa de ACID
&lt;/h3&gt;

&lt;p&gt;95% das minhas queries são:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Me dá esse arquivo"&lt;/li&gt;
&lt;li&gt;"Lista arquivos dessa categoria"&lt;/li&gt;
&lt;li&gt;"Deleta isso"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Nada disso precisa de transações complexas.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Prefixos = Índices Grátis
&lt;/h3&gt;

&lt;p&gt;Organizar por &lt;code&gt;categoria/subcategoria/arquivo&lt;/code&gt; é essencialmente criar índices.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;despesas-mensais/2024/10/fatura.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso me permite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Listar tudo de outubro: &lt;code&gt;Prefix="despesas-mensais/2024/10/"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Listar tudo de 2024: &lt;code&gt;Prefix="despesas-mensais/2024/"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Listar todas despesas: &lt;code&gt;Prefix="despesas-mensais/"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Infraestrutura Brasileira Importa
&lt;/h3&gt;

&lt;p&gt;MagaluCloud com datacenter em SP = &lt;strong&gt;&amp;lt;50ms de latência&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
AWS US East = &lt;strong&gt;180ms de latência&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Para usuário final, isso é &lt;strong&gt;perceptível&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Construir NimbusVault me ensinou que &lt;strong&gt;nem sempre precisamos da solução mais complexa&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Antes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostgreSQL (R$80/mês)&lt;/li&gt;
&lt;li&gt;Redis para cache (R$40/mês)
&lt;/li&gt;
&lt;li&gt;VM com 2GB RAM (R$60/mês)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Total: R$180/mês&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Agora:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MagaluCloud Object Storage (~R$1/mês)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Total: R$1/mês&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Economia: R$2.148/ano&lt;/strong&gt; 💰&lt;/p&gt;

&lt;p&gt;E o melhor: &lt;strong&gt;menos coisas para quebrar&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Não preciso me preocupar com:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backups (S3 é 11 noves de durabilidade)&lt;/li&gt;
&lt;li&gt;Escalabilidade (S3 escala infinito)&lt;/li&gt;
&lt;li&gt;Manutenção (zero administração)&lt;/li&gt;
&lt;li&gt;Atualizações (API não muda)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Código Aberto
&lt;/h2&gt;

&lt;p&gt;Todo o código está no GitHub:&lt;br&gt;
&lt;strong&gt;&lt;a href="https://github.com/mcruvinel/NimbusVault" rel="noopener noreferrer"&gt;github.com/mcruvinel/NimbusVault&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sinta-se livre para:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ Dar uma estrela&lt;/li&gt;
&lt;li&gt;🍴 Fazer fork&lt;/li&gt;
&lt;li&gt;🐛 Reportar bugs&lt;/li&gt;
&lt;li&gt;💡 Sugerir features&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Experimente Você Mesmo
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Clone&lt;/span&gt;
git clone https://github.com/mcruvinel/NimbusVault
&lt;span class="nb"&gt;cd &lt;/span&gt;NimbusVault

&lt;span class="c"&gt;# 2. Configure&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;span class="c"&gt;# Edite .env com suas credenciais MagaluCloud&lt;/span&gt;

&lt;span class="c"&gt;# 3. Run&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# 4. Acesse&lt;/span&gt;
open http://localhost:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Como obter credenciais:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Acesse &lt;a href="https://console.magalu.cloud" rel="noopener noreferrer"&gt;console.magalu.cloud&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Crie um bucket em Object Storage&lt;/li&gt;
&lt;li&gt;Gere um par de chaves de acesso&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Pensamentos Finais
&lt;/h2&gt;

&lt;p&gt;Este projeto começou como um experimento de fim de semana e virou minha solução real de gestão financeira.&lt;/p&gt;

&lt;p&gt;Prova que &lt;strong&gt;ferramentas simples, usadas criativamente, resolvem problemas complexos&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Às vezes, você só precisa de &lt;strong&gt;um bucket bem organizado&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Gostou? Compartilhe este post!&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/mcruvinel" rel="noopener noreferrer"&gt;@mcruvinel&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blog:&lt;/strong&gt; &lt;a href="https://mount-log.hashnode.dev/" rel="noopener noreferrer"&gt;mount.log&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Documentação&lt;/strong&gt; &lt;a href="https://docs.magalu.cloud/docs/storage/object-storage/overview" rel="noopener noreferrer"&gt;MagaluCloud ObjectStorage&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; #Python #FastAPI #React #S3 #MagaluCloud #ObjectStorage #CloudStorage #FinTech #OpenSource #Serverless&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>showdev</category>
      <category>magalucloud</category>
    </item>
  </channel>
</rss>
