<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[rcmisk: Learn to Build Django &amp; React Apps in Public | Step-by-Step Guides, Tutorials &amp; Real-World Projects for Aspiring Developers"]]></title><description><![CDATA[Join rcmisk for hands-on tutorials and guides on building Django &amp; React apps. Learn by following real-world projects and coding in public.]]></description><link>https://rcmisk.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1755658003706/8b88390d-3441-480f-b38d-c8aa0398cfd0.png</url><title>rcmisk: Learn to Build Django &amp;amp; React Apps in Public | Step-by-Step Guides, Tutorials &amp;amp; Real-World Projects for Aspiring Developers&quot;</title><link>https://rcmisk.com</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 11 Apr 2026 04:18:05 GMT</lastBuildDate><atom:link href="https://rcmisk.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Gemini Nano Banana Guide: Turn Ideas into Cinematic AI GIFs]]></title><description><![CDATA[🍌 How to Use Gemini’s Nano Banana AI Image Tool: A Complete Tutorial
The creator economy just got a clever new tool: Gemini’s Nano Banana.It’s Gemini’s lightweight AI image engine that turns short prompts into looping, stylized GIFs and cinematic sn...]]></description><link>https://rcmisk.com/gemini-nano-banana-guide-turn-ideas-into-cinematic-ai-gifs</link><guid isPermaLink="true">https://rcmisk.com/gemini-nano-banana-guide-turn-ideas-into-cinematic-ai-gifs</guid><category><![CDATA[gemini]]></category><category><![CDATA[google gemini]]></category><category><![CDATA[nanobanana]]></category><dc:creator><![CDATA[rcmisk]]></dc:creator><pubDate>Fri, 29 Aug 2025 03:43:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/fpZZEV0uQwA/upload/c7c5342a95ce647ac7085c2953c3c641.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-how-to-use-geminis-nano-banana-ai-image-tool-a-complete-tutorial">🍌 How to Use Gemini’s <em>Nano Banana</em> AI Image Tool: A Complete Tutorial</h1>
<p>The creator economy just got a clever new tool: Gemini’s <a target="_blank" href="https://developers.googleblog.com/en/introducing-gemini-2-5-flash-image/"><strong>Nano Banana</strong></a>.<br />It’s Gemini’s lightweight AI image engine that turns short prompts into looping, stylized GIFs and cinematic snippets.</p>
<p>Think meme generator meets film-school aesthetic.<br />No $100k editor. No team of producers. Just a laptop, WiFi, and your taste.</p>
<p>In this guide, I’ll walk you through how to use Nano Banana, and show off some <strong>cool use cases</strong> to spark ideas.</p>
<hr />
<h2 id="heading-step-1-access-nano-banana">🚀 Step 1: Access Nano Banana</h2>
<ul>
<li><p>Head to the <strong>Nano Banana dashboard</strong> (currently in beta, invite-only for some users).</p>
</li>
<li><p>Sign in with your Google account.</p>
</li>
<li><p>You’ll land on a minimal interface: a text box for your prompt, a preset library, and export options.</p>
</li>
</ul>
<p>👉 No confusing menus — the tool is designed to be fast and playful.</p>
<hr />
<h2 id="heading-step-2-pick-a-preset">🎨 Step 2: Pick a Preset</h2>
<p>Nano Banana comes loaded with <strong>cinematic presets</strong> that give your GIFs instant personality.</p>
<p>Some examples you’ll find:</p>
<ul>
<li><p>🎞 Noir — moody, black-and-white film look.</p>
</li>
<li><p>🌈 Vaporwave — retro arcade vibes.</p>
</li>
<li><p>🌌 Neon Anime — colorful, high-energy loops.</p>
</li>
<li><p>⚡ Glitch — edgy, broken-screen effects.</p>
</li>
</ul>
<p>Presets do the heavy lifting for tone. You just bring the story.</p>
<hr />
<h2 id="heading-step-3-write-a-punchy-prompt">✍️ Step 3: Write a Punchy Prompt</h2>
<p>Unlike long AI text generators, Nano Banana thrives on <strong>short, meme-ready prompts</strong>.</p>
<p>Examples:</p>
<ul>
<li><p><em>“When your SaaS gets its first paying user 🍾”</em></p>
</li>
<li><p><em>“AI coding at 3am feels like hacking the Matrix”</em></p>
</li>
<li><p><em>“Me, deploying to prod without testing 🔥🐒”</em></p>
</li>
</ul>
<p>👉 Keep it under 10 words when possible. The shorter the setup, the harder the punchline lands.</p>
<hr />
<h2 id="heading-step-4-generate-export">🛠 Step 4: Generate + Export</h2>
<ul>
<li><p>Click <strong>Generate</strong> → Nano Banana creates multiple GIF variants.</p>
</li>
<li><p>Review your options → pick your favorite vibe.</p>
</li>
<li><p>Hit <strong>Export</strong> → choose from 3s, 6s, or 9s loops, already optimized for X/Twitter, Reddit, or TikTok.</p>
</li>
</ul>
<p>💡 Pro tip: batch 5–10 prompts at once. Post only the 1–2 that instantly feel “scroll-stopping.”</p>
<hr />
<h2 id="heading-cool-example-use-cases">🌟 Cool Example Use Cases</h2>
<h3 id="heading-1-indie-hacker-growth-hacks">1. Indie Hacker Growth Hacks</h3>
<p>Show progress in a way that’s fun and shareable.</p>
<ul>
<li><p><em>“Day 20 of #buildinpublic — first Stripe payment processed 💳🎉”</em> with a confetti preset.</p>
</li>
<li><p><em>“Bug in prod again? Same.”</em> with a looping glitch animation.</p>
</li>
</ul>
<h3 id="heading-2-ai-memes-for-tech-twitter">2. AI Memes for Tech Twitter</h3>
<p>Remix trending conversations into cinematic GIFs.</p>
<ul>
<li><p><em>“Over-engineering vs. ugly MVPs”</em> → vaporwave split-screen.</p>
</li>
<li><p><em>“Claude vs. Gemini vs. GPT”</em> → arcade-style boxing match.</p>
</li>
</ul>
<h3 id="heading-3-community-packs">3. Community Packs</h3>
<p>Create branded reaction GIFs for your Discord or Slack.</p>
<ul>
<li><p>“GM 🌞”</p>
</li>
<li><p>“Deploying…”</p>
</li>
<li><p>“Brainstorm mode: ON”</p>
</li>
</ul>
<p>These GIF packs double as inside jokes <em>and</em> marketing assets.</p>
<hr />
<h2 id="heading-why-nano-banana-matters">📈 Why Nano Banana Matters</h2>
<p>The new creator economy isn’t about budgets. It’s about <strong>taste, cadence, and culture</strong>.</p>
<p>Nano Banana gives solo founders, indie hackers, and creators the ability to:</p>
<ul>
<li><p>Reframe memes as <em>stylized cinema</em>.</p>
</li>
<li><p>Turn raw ideas into polished, shareable content in minutes.</p>
</li>
<li><p>Compete with big-budget teams by leaning on creativity, not cash.</p>
</li>
</ul>
<p>Virality on demand. Culture at scale. All from your laptop.</p>
<hr />
<h2 id="heading-final-takeaway">✅ Final Takeaway</h2>
<p>Nano Banana isn’t just another AI tool — it’s a cultural amplifier.</p>
<p>Start small:</p>
<ul>
<li><p>Write one line.</p>
</li>
<li><p>Pick one preset.</p>
</li>
<li><p>Export one GIF.</p>
</li>
</ul>
<p>That’s all it takes to ship your first piece of Nano Banana content.</p>
<p>Imagine the possibilities!</p>
]]></content:encoded></item><item><title><![CDATA[Mastering Parallel Coding Agents in Claude Code — A Copy-Paste Guide]]></title><description><![CDATA[Claude Code Agents
Want your main agent to keep coding while a sub-agent plans the next feature—and a master agent keeps everyone in sync? This post gives you a clean, from-scratch setup you can copy, run, and share.
You’ll end up with three Claude C...]]></description><link>https://rcmisk.com/mastering-parallel-coding-agents-in-claude-code-a-copy-paste-guide</link><guid isPermaLink="true">https://rcmisk.com/mastering-parallel-coding-agents-in-claude-code-a-copy-paste-guide</guid><category><![CDATA[claude-code]]></category><category><![CDATA[claude.ai]]></category><category><![CDATA[coding]]></category><category><![CDATA[#ai-tools]]></category><category><![CDATA[AI]]></category><category><![CDATA[ai agents]]></category><category><![CDATA[claude]]></category><category><![CDATA[automation]]></category><dc:creator><![CDATA[rcmisk]]></dc:creator><pubDate>Sat, 23 Aug 2025 16:06:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/_jg8xh2SsXQ/upload/b574083cc4b6b27df6609bc9bbf73942.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-claude-code-agents">Claude Code Agents</h1>
<p>Want your main agent to keep coding while a sub-agent plans the next feature—and a master agent keeps everyone in sync? This post gives you a <strong>clean, from-scratch setup</strong> you can copy, run, and share.</p>
<p>You’ll end up with three Claude Code sessions running in parallel on the <strong>same project</strong>:</p>
<ul>
<li><p><strong>main-dev</strong> → implements code (locked to <code>/src</code> and <code>/tests</code>)</p>
</li>
<li><p><strong>feature-planner</strong> → writes PRDs (locked to <code>/docs/feature-plans/</code>)</p>
</li>
<li><p><strong>master-agent</strong> → orchestrates (reads/writes <code>/ops</code>, summarizes status)</p>
</li>
</ul>
<p>The system uses <strong>guardrail hooks</strong> to enforce boundaries and a tiny renderer to stamp agent templates from your inputs.</p>
<hr />
<h2 id="heading-tldr">TL;DR</h2>
<ol>
<li><p>Create a folder and drop in the <strong>bootstrap script</strong> below.</p>
</li>
<li><p>Run it to scaffold folders, hooks, templates.</p>
</li>
<li><p>Render agents from inputs.</p>
</li>
<li><p>Open three Claude Code sessions (main-dev, feature-planner, master-agent).</p>
</li>
<li><p>Paste the prompts. Build in parallel. 🎯</p>
</li>
</ol>
<hr />
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p>macOS or Linux shell</p>
</li>
<li><p><code>jq</code> and <code>python3</code> installed</p>
<ul>
<li><p>macOS (Homebrew): <code>brew install jq python</code></p>
</li>
<li><p>Ubuntu/Debian: <code>sudo apt-get install -y jq python3</code></p>
</li>
</ul>
</li>
</ul>
<hr />
<h2 id="heading-step-1-create-the-project-amp-add-bootstrapshhttpbootstrapsh">Step 1 — Create the project &amp; add <a target="_blank" href="http://bootstrap.sh"><code>bootstrap.sh</code></a></h2>
<p>In an empty directory:</p>
<pre><code class="lang-markdown">mkdir my-agents-project
cd my-agents-project
</code></pre>
<p>Create <a target="_blank" href="http://bootstrap.sh"><code>bootstrap.sh</code></a> and paste <strong>all</strong> of this:</p>
<pre><code class="lang-markdown"><span class="hljs-section">#!/usr/bin/env bash</span>
set -euo pipefail

<span class="hljs-section"># --- helpers ---</span>
write() { mkdir -p "$(dirname "$1")"; cat &gt; "$1"; }

<span class="hljs-section"># --- tree ---</span>
mkdir -p .claude/{agents-templates,hooks} docs/feature-plans docs/release-notes ops/inputs scripts src tests

<span class="hljs-section"># --- policy (guardrails) ---</span>
write ops/policy.json <span class="xml"><span class="hljs-tag">&lt;&lt;'<span class="hljs-attr">JSON</span>'
{
  "<span class="hljs-attr">writeAllow</span>"<span class="hljs-attr">:</span> {
    "<span class="hljs-attr">master-agent</span>"<span class="hljs-attr">:</span> ["<span class="hljs-attr">ops</span>/*"],
    "<span class="hljs-attr">feature-planner</span>"<span class="hljs-attr">:</span> ["<span class="hljs-attr">docs</span>/<span class="hljs-attr">feature-plans</span>/*"],
    "<span class="hljs-attr">main-dev</span>"<span class="hljs-attr">:</span> ["<span class="hljs-attr">src</span>/*","<span class="hljs-attr">tests</span>/*"]
  },
  "<span class="hljs-attr">inboxNotifyAgents</span>"<span class="hljs-attr">:</span> ["<span class="hljs-attr">feature-planner</span>"],
  "<span class="hljs-attr">inboxPath</span>"<span class="hljs-attr">:</span> "<span class="hljs-attr">docs</span>/<span class="hljs-attr">feature-plans</span>/<span class="hljs-attr">INBOX.md</span>",
  "<span class="hljs-attr">eventsLogPath</span>"<span class="hljs-attr">:</span> "<span class="hljs-attr">ops</span>/<span class="hljs-attr">events.log</span>"
}
<span class="hljs-attr">JSON</span>

# <span class="hljs-attr">---</span> <span class="hljs-attr">hooks</span> (<span class="hljs-attr">require</span> <span class="hljs-attr">jq</span>) <span class="hljs-attr">---</span>
<span class="hljs-attr">write</span> <span class="hljs-attr">.claude</span>/<span class="hljs-attr">hooks</span>/<span class="hljs-attr">pretooluse.sh</span> &lt;&lt;'<span class="hljs-attr">BASH</span>'
#!/<span class="hljs-attr">usr</span>/<span class="hljs-attr">bin</span>/<span class="hljs-attr">env</span> <span class="hljs-attr">bash</span>
<span class="hljs-attr">set</span> <span class="hljs-attr">-euo</span> <span class="hljs-attr">pipefail</span>
<span class="hljs-attr">TOOL</span>=<span class="hljs-string">"${CLAUDE_TOOL_NAME:-}"</span>; <span class="hljs-attr">AGENT</span>=<span class="hljs-string">"${CLAUDE_AGENT_NAME:-}"</span>
<span class="hljs-attr">ARGS</span>=<span class="hljs-string">"${CLAUDE_TOOL_ARGS_JSON:-}"</span>; <span class="hljs-attr">POLICY</span>=<span class="hljs-string">"ops/policy.json"</span>
<span class="hljs-attr">PATH_TARGET</span>=<span class="hljs-string">"$(printf '%s' "</span>$<span class="hljs-attr">ARGS</span>" | <span class="hljs-attr">jq</span> <span class="hljs-attr">-r</span> '<span class="hljs-attr">.path</span> // <span class="hljs-attr">empty</span>' <span class="hljs-attr">2</span>&gt;</span></span>/dev/null || true)"

if [[ "$TOOL" =~ ^(Write|Edit|MultiEdit)$ ]]; then
  MAP=$(jq -r --arg a "$AGENT" '.writeAllow[<span class="hljs-string">$a</span>][<span class="hljs-symbol"></span>]?' "$POLICY" 2&gt;/dev/null || true)
  if [ -z "$MAP" ]; then echo "No write policy for $AGENT"; exit 1; fi
  ALLOWED=false
  while IFS= read -r pat; do
<span class="hljs-code">    case "$PATH_TARGET" in $pat) ALLOWED=true; break;; esac
  done &lt;&lt;&lt; "$MAP"
  if [ "$ALLOWED" != true ]; then
    echo "Write blocked by policy. Agent=$AGENT Path=$PATH_TARGET"
    exit 1
  fi
fi
BASH
</span>
write .claude/hooks/posttooluse.sh <span class="xml"><span class="hljs-tag">&lt;&lt;'<span class="hljs-attr">BASH</span>'
#!/<span class="hljs-attr">usr</span>/<span class="hljs-attr">bin</span>/<span class="hljs-attr">env</span> <span class="hljs-attr">bash</span>
<span class="hljs-attr">set</span> <span class="hljs-attr">-euo</span> <span class="hljs-attr">pipefail</span>
<span class="hljs-attr">TOOL</span>=<span class="hljs-string">"${CLAUDE_TOOL_NAME:-}"</span>; <span class="hljs-attr">AGENT</span>=<span class="hljs-string">"${CLAUDE_AGENT_NAME:-}"</span>
<span class="hljs-attr">ARGS</span>=<span class="hljs-string">"${CLAUDE_TOOL_ARGS_JSON:-}"</span>; <span class="hljs-attr">POLICY</span>=<span class="hljs-string">"ops/policy.json"</span>
<span class="hljs-attr">EVENTS</span>=<span class="hljs-string">"$(jq -r '.eventsLogPath' "</span>$<span class="hljs-attr">POLICY</span>")"
<span class="hljs-attr">INBOX</span>=<span class="hljs-string">"$(jq -r '.inboxPath' "</span>$<span class="hljs-attr">POLICY</span>")"
<span class="hljs-attr">NEEDS_INBOX</span>=<span class="hljs-string">$(jq</span> <span class="hljs-attr">-r</span> <span class="hljs-attr">--arg</span> <span class="hljs-attr">a</span> "$<span class="hljs-attr">AGENT</span>" '<span class="hljs-attr">.inboxNotifyAgents</span> | <span class="hljs-attr">index</span>($<span class="hljs-attr">a</span>) | <span class="hljs-attr">if</span> <span class="hljs-attr">.</span>==<span class="hljs-string">null</span> <span class="hljs-attr">then</span> "<span class="hljs-attr">no</span>" <span class="hljs-attr">else</span> "<span class="hljs-attr">yes</span>" <span class="hljs-attr">end</span>' "$<span class="hljs-attr">POLICY</span>")

<span class="hljs-attr">if</span> [ "$<span class="hljs-attr">TOOL</span>" = <span class="hljs-string">"Write"</span> ]; <span class="hljs-attr">then</span>
  <span class="hljs-attr">FILE</span>=<span class="hljs-string">"$(printf '%s' "</span>$<span class="hljs-attr">ARGS</span>" | <span class="hljs-attr">jq</span> <span class="hljs-attr">-r</span> '<span class="hljs-attr">.path</span> // <span class="hljs-attr">empty</span>' <span class="hljs-attr">2</span>&gt;</span></span>/dev/null || true)"
  if [ -n "$FILE" ]; then
<span class="hljs-code">    TS="$(date -u +'%Y-%m-%d %H:%M:%SZ')"
    echo "[$TS] $AGENT wrote: $FILE" &gt;&gt; "$EVENTS"
    if [ "$NEEDS_INBOX" = "yes" ]; then
      echo "- [$TS] $AGENT updated: $FILE" &gt;&gt; "$INBOX"
    fi
  fi
fi
BASH
</span>
chmod +x .claude/hooks/<span class="hljs-emphasis">*.sh

# --- agent templates ---
write .claude/agents-templates/master-agent.tmpl.md <span class="xml"><span class="hljs-tag">&lt;&lt;'<span class="hljs-attr">MD</span>'
<span class="hljs-attr">---</span>
<span class="hljs-attr">name:</span> <span class="hljs-attr">master-agent</span>
<span class="hljs-attr">description:</span> <span class="hljs-attr">Orchestrates</span> <span class="hljs-attr">sub-agents.</span> <span class="hljs-attr">Writes</span> <span class="hljs-attr">only</span> <span class="hljs-attr">to</span> /<span class="hljs-attr">ops</span> <span class="hljs-attr">per</span> <span class="hljs-attr">policy.</span>
<span class="hljs-attr">tools:</span> <span class="hljs-attr">Read</span>, <span class="hljs-attr">Write</span>, <span class="hljs-attr">Grep</span>, <span class="hljs-attr">Glob</span>
<span class="hljs-attr">---</span>
<span class="hljs-attr">Read</span> /<span class="hljs-attr">ops</span>/<span class="hljs-attr">agent-state.json</span>, <span class="hljs-attr">last</span> <span class="hljs-attr">50</span> <span class="hljs-attr">lines</span> <span class="hljs-attr">of</span> /<span class="hljs-attr">ops</span>/<span class="hljs-attr">events.log</span>, <span class="hljs-attr">and</span> <span class="hljs-attr">last</span> <span class="hljs-attr">20</span> <span class="hljs-attr">lines</span> <span class="hljs-attr">of</span> <span class="hljs-attr">docs</span>/<span class="hljs-attr">feature-plans</span>/<span class="hljs-attr">INBOX.md.</span>
<span class="hljs-attr">Update</span> <span class="hljs-attr">statuses</span> <span class="hljs-attr">when</span> <span class="hljs-attr">there</span> <span class="hljs-attr">is</span> <span class="hljs-attr">evidence</span> (<span class="hljs-attr">new</span> <span class="hljs-attr">PRD</span>, <span class="hljs-attr">new</span> <span class="hljs-attr">branch</span>, <span class="hljs-attr">QA</span> <span class="hljs-attr">report</span>)<span class="hljs-attr">.</span> <span class="hljs-attr">Merge</span> /<span class="hljs-attr">ops</span>/<span class="hljs-attr">agent-state.json</span> <span class="hljs-attr">and</span> <span class="hljs-attr">append</span> <span class="hljs-attr">a</span> <span class="hljs-attr">Standup</span> <span class="hljs-attr">line</span> <span class="hljs-attr">to</span> /<span class="hljs-attr">ops</span>/<span class="hljs-attr">events.log.</span>
<span class="hljs-attr">Return</span> <span class="hljs-attr">a</span> <span class="hljs-attr">7-bullet</span> <span class="hljs-attr">executive</span> <span class="hljs-attr">summary</span> <span class="hljs-attr">with</span> <span class="hljs-attr">links.</span>
<span class="hljs-attr">MD</span>

<span class="hljs-attr">write</span> <span class="hljs-attr">.claude</span>/<span class="hljs-attr">agents-templates</span>/<span class="hljs-attr">feature-planner.tmpl.md</span> &lt;&lt;'<span class="hljs-attr">MD</span>'
<span class="hljs-attr">---</span>
<span class="hljs-attr">name:</span> <span class="hljs-attr">feature-planner</span>
<span class="hljs-attr">description:</span> <span class="hljs-attr">Plans</span> <span class="hljs-attr">features.</span> <span class="hljs-attr">Writes</span> <span class="hljs-attr">PRDs</span> <span class="hljs-attr">only</span> <span class="hljs-attr">to</span> /<span class="hljs-attr">docs</span>/<span class="hljs-attr">feature-plans</span>/<span class="hljs-attr">.</span>
<span class="hljs-attr">tools:</span> <span class="hljs-attr">Read</span>, <span class="hljs-attr">Write</span>, <span class="hljs-attr">Grep</span>, <span class="hljs-attr">Glob</span>
<span class="hljs-attr">---</span>
<span class="hljs-attr">Plan</span> "{{<span class="hljs-attr">FEATURE_NAME</span>}}" <span class="hljs-attr">for</span> "{{<span class="hljs-attr">PROJECT_NAME</span>}}"<span class="hljs-attr">.</span>
<span class="hljs-attr">1</span>) <span class="hljs-attr">Ask</span> <span class="hljs-attr">7</span> <span class="hljs-attr">brief</span> <span class="hljs-attr">scoping</span> <span class="hljs-attr">questions.</span>
<span class="hljs-attr">2</span>) <span class="hljs-attr">Copy</span> /<span class="hljs-attr">docs</span>/<span class="hljs-attr">feature-plans</span>/<span class="hljs-attr">PRD_TEMPLATE.md</span> <span class="hljs-attr">to</span> /<span class="hljs-attr">docs</span>/<span class="hljs-attr">feature-plans</span>/<span class="hljs-attr">PLN_</span>{{<span class="hljs-attr">FEATURE_SLUG</span>}}<span class="hljs-attr">_</span>{{<span class="hljs-attr">DATE</span>}}<span class="hljs-attr">.md</span> <span class="hljs-attr">and</span> <span class="hljs-attr">fill</span> <span class="hljs-attr">sections.</span>
<span class="hljs-attr">3</span>) <span class="hljs-attr">Append</span> <span class="hljs-attr">a</span> <span class="hljs-attr">timestamped</span> <span class="hljs-attr">entry</span> <span class="hljs-attr">to</span> <span class="hljs-attr">Changelog</span> <span class="hljs-attr">on</span> <span class="hljs-attr">each</span> <span class="hljs-attr">update.</span>
<span class="hljs-attr">4</span>) <span class="hljs-attr">Return</span> <span class="hljs-attr">the</span> <span class="hljs-attr">PRD</span> <span class="hljs-attr">path</span> + <span class="hljs-attr">5</span> <span class="hljs-attr">bullets</span> (<span class="hljs-attr">highlights</span>/<span class="hljs-attr">blockers</span>/<span class="hljs-attr">next</span>)<span class="hljs-attr">.</span>
<span class="hljs-attr">Write</span> <span class="hljs-attr">only</span> <span class="hljs-attr">under</span> /<span class="hljs-attr">docs</span>/<span class="hljs-attr">feature-plans</span>/<span class="hljs-attr">.</span>
<span class="hljs-attr">MD</span>

<span class="hljs-attr">write</span> <span class="hljs-attr">.claude</span>/<span class="hljs-attr">agents-templates</span>/<span class="hljs-attr">main-dev.tmpl.md</span> &lt;&lt;'<span class="hljs-attr">MD</span>'
<span class="hljs-attr">---</span>
<span class="hljs-attr">name:</span> <span class="hljs-attr">main-dev</span>
<span class="hljs-attr">description:</span> <span class="hljs-attr">Implements</span> <span class="hljs-attr">current</span> <span class="hljs-attr">sprint</span> <span class="hljs-attr">items</span> <span class="hljs-attr">in</span> /<span class="hljs-attr">src</span> <span class="hljs-attr">and</span> /<span class="hljs-attr">tests</span> <span class="hljs-attr">only.</span> <span class="hljs-attr">Reads</span> <span class="hljs-attr">INBOX</span> + <span class="hljs-attr">PRDs</span> <span class="hljs-attr">for</span> <span class="hljs-attr">acceptance</span> <span class="hljs-attr">criteria.</span>
<span class="hljs-attr">tools:</span> <span class="hljs-attr">Read</span>, <span class="hljs-attr">Write</span>, <span class="hljs-attr">Grep</span>, <span class="hljs-attr">Glob</span>
<span class="hljs-attr">---</span>
<span class="hljs-attr">Before</span> <span class="hljs-attr">coding:</span> <span class="hljs-attr">read</span> <span class="hljs-attr">docs</span>/<span class="hljs-attr">feature-plans</span>/<span class="hljs-attr">INBOX.md</span> (<span class="hljs-attr">last</span> <span class="hljs-attr">15</span> <span class="hljs-attr">lines</span>) <span class="hljs-attr">and</span> <span class="hljs-attr">latest</span> <span class="hljs-attr">PRD</span> <span class="hljs-attr">for</span> "{{<span class="hljs-attr">FEATURE_NAME</span>}}"<span class="hljs-attr">.</span>
<span class="hljs-attr">Summarize</span> <span class="hljs-attr">AC</span> <span class="hljs-attr">that</span> <span class="hljs-attr">impact</span> <span class="hljs-attr">code.</span> <span class="hljs-attr">Implement</span> <span class="hljs-attr">increment</span> <span class="hljs-attr">in</span> /<span class="hljs-attr">src</span> (<span class="hljs-attr">create</span>/<span class="hljs-attr">update</span> <span class="hljs-attr">tests</span>)<span class="hljs-attr">.</span> <span class="hljs-attr">Output</span> <span class="hljs-attr">a</span> <span class="hljs-attr">5-item</span> <span class="hljs-attr">commit</span> <span class="hljs-attr">checklist.</span>
<span class="hljs-attr">MD</span>

# <span class="hljs-attr">---</span> <span class="hljs-attr">renderer</span> <span class="hljs-attr">---</span>
<span class="hljs-attr">write</span> <span class="hljs-attr">scripts</span>/<span class="hljs-attr">render_templates.py</span> &lt;&lt;'<span class="hljs-attr">PY</span>'
#!/<span class="hljs-attr">usr</span>/<span class="hljs-attr">bin</span>/<span class="hljs-attr">env</span> <span class="hljs-attr">python3</span>
<span class="hljs-attr">import</span> <span class="hljs-attr">os</span>, <span class="hljs-attr">re</span>, <span class="hljs-attr">sys</span>, <span class="hljs-attr">json</span>, <span class="hljs-attr">glob</span>
<span class="hljs-attr">ROOT</span>=<span class="hljs-string">os.path.abspath(os.path.join(os.path.dirname(__file__),</span>"<span class="hljs-attr">..</span>"))
<span class="hljs-attr">def</span> <span class="hljs-attr">render</span>(<span class="hljs-attr">t</span>,<span class="hljs-attr">c</span>)<span class="hljs-attr">:</span> <span class="hljs-attr">return</span> <span class="hljs-attr">re.sub</span>(<span class="hljs-attr">r</span>"\{\{([<span class="hljs-attr">A-Z0-9_</span>]+)\}\}", <span class="hljs-attr">lambda</span> <span class="hljs-attr">m:str</span>(<span class="hljs-attr">c.get</span>(<span class="hljs-attr">m.group</span>(<span class="hljs-attr">1</span>),<span class="hljs-attr">m.group</span>(<span class="hljs-attr">0</span>))), <span class="hljs-attr">t</span>)

<span class="hljs-attr">if</span> <span class="hljs-attr">len</span>(<span class="hljs-attr">sys.argv</span>)&lt;<span class="hljs-attr">2:</span>
    <span class="hljs-attr">print</span>("<span class="hljs-attr">usage:</span> <span class="hljs-attr">render_templates.py</span> <span class="hljs-attr">ops</span>/<span class="hljs-attr">inputs</span>/<span class="hljs-attr">current.json</span>")
    <span class="hljs-attr">sys.exit</span>(<span class="hljs-attr">1</span>)

<span class="hljs-attr">with</span> <span class="hljs-attr">open</span>(<span class="hljs-attr">sys.argv</span>[<span class="hljs-attr">1</span>]) <span class="hljs-attr">as</span> <span class="hljs-attr">f:</span>
    <span class="hljs-attr">ctx</span>=<span class="hljs-string">json.load(f)</span>

<span class="hljs-attr">os.makedirs</span>(<span class="hljs-attr">os.path.join</span>(<span class="hljs-attr">ROOT</span>,"<span class="hljs-attr">.claude</span>","<span class="hljs-attr">agents</span>"),<span class="hljs-attr">exist_ok</span>=<span class="hljs-string">True)</span>

<span class="hljs-attr">for</span> <span class="hljs-attr">p</span> <span class="hljs-attr">in</span> <span class="hljs-attr">glob.glob</span>(<span class="hljs-attr">os.path.join</span>(<span class="hljs-attr">ROOT</span>,"<span class="hljs-attr">.claude</span>","<span class="hljs-attr">agents-templates</span>","*<span class="hljs-attr">.tmpl.md</span>"))<span class="hljs-attr">:</span>
    <span class="hljs-attr">out</span>=<span class="hljs-string">render(open(p).read(),ctx)</span>
    <span class="hljs-attr">o</span>=<span class="hljs-string">os.path.join(ROOT,</span>"<span class="hljs-attr">.claude</span>","<span class="hljs-attr">agents</span>",<span class="hljs-attr">os.path.basename</span>(<span class="hljs-attr">p</span>)<span class="hljs-attr">.replace</span>("<span class="hljs-attr">.tmpl</span>",""))
    <span class="hljs-attr">open</span>(<span class="hljs-attr">o</span>,"<span class="hljs-attr">w</span>")<span class="hljs-attr">.write</span>(<span class="hljs-attr">out</span>)
    <span class="hljs-attr">print</span>("<span class="hljs-attr">Rendered</span> <span class="hljs-attr">agent:</span>",<span class="hljs-attr">os.path.basename</span>(<span class="hljs-attr">o</span>))

# <span class="hljs-attr">render</span> <span class="hljs-attr">PRD</span> <span class="hljs-attr">template</span> <span class="hljs-attr">placeholders</span>
<span class="hljs-attr">prd</span>=<span class="hljs-string">os.path.join(ROOT,</span>"<span class="hljs-attr">docs</span>","<span class="hljs-attr">feature-plans</span>","<span class="hljs-attr">PRD_TEMPLATE.md</span>")
<span class="hljs-attr">if</span> <span class="hljs-attr">os.path.exists</span>(<span class="hljs-attr">prd</span>)<span class="hljs-attr">:</span>
    <span class="hljs-attr">open</span>(<span class="hljs-attr">prd</span>,"<span class="hljs-attr">w</span>")<span class="hljs-attr">.write</span>(<span class="hljs-attr">render</span>(<span class="hljs-attr">open</span>(<span class="hljs-attr">prd</span>)<span class="hljs-attr">.read</span>(),<span class="hljs-attr">ctx</span>))
    <span class="hljs-attr">print</span>("<span class="hljs-attr">Rendered:</span> <span class="hljs-attr">docs</span>/<span class="hljs-attr">feature-plans</span>/<span class="hljs-attr">PRD_TEMPLATE.md</span>")

<span class="hljs-attr">print</span>("<span class="hljs-attr">Done.</span>")
<span class="hljs-attr">PY</span>

<span class="hljs-attr">chmod</span> +<span class="hljs-attr">x</span> <span class="hljs-attr">scripts</span>/<span class="hljs-attr">render_templates.py</span>

# <span class="hljs-attr">---</span> <span class="hljs-attr">seeds</span>/<span class="hljs-attr">inputs</span>/<span class="hljs-attr">state</span> <span class="hljs-attr">---</span>
<span class="hljs-attr">write</span> <span class="hljs-attr">ops</span>/<span class="hljs-attr">inputs</span>/<span class="hljs-attr">current.json</span> &lt;&lt;'<span class="hljs-attr">JSON</span>'
{
  "<span class="hljs-attr">PROJECT_NAME</span>"<span class="hljs-attr">:</span>"<span class="hljs-attr">SaaS</span> <span class="hljs-attr">Boilerplate</span>",
  "<span class="hljs-attr">FEATURE_NAME</span>"<span class="hljs-attr">:</span>"<span class="hljs-attr">SaaS</span> <span class="hljs-attr">Boilerplate</span> <span class="hljs-attr">MVP</span>",
  "<span class="hljs-attr">FEATURE_SLUG</span>"<span class="hljs-attr">:</span>"<span class="hljs-attr">saas-boilerplate-mvp</span>",
  "<span class="hljs-attr">DATE</span>"<span class="hljs-attr">:</span>"<span class="hljs-attr">20250823</span>",
  "<span class="hljs-attr">DATE_ISO</span>"<span class="hljs-attr">:</span>"<span class="hljs-attr">2025-08-23T00:00:00Z</span>"
}
<span class="hljs-attr">JSON</span>

<span class="hljs-attr">write</span> <span class="hljs-attr">ops</span>/<span class="hljs-attr">agent-state.json</span> &lt;&lt;'<span class="hljs-attr">JSON</span>'
{ "<span class="hljs-attr">tasks</span>"<span class="hljs-attr">:</span> [], "<span class="hljs-attr">updatedAt</span>"<span class="hljs-attr">:</span> "<span class="hljs-attr">1970-01-01T00:00:00Z</span>" }
<span class="hljs-attr">JSON</span>

<span class="hljs-attr">write</span> <span class="hljs-attr">ops</span>/<span class="hljs-attr">events.log</span> &lt;&lt;'<span class="hljs-attr">LOG</span>'
[<span class="hljs-attr">bootstrap</span>] <span class="hljs-attr">initialized</span>
<span class="hljs-attr">LOG</span>

# <span class="hljs-attr">---</span> <span class="hljs-attr">docs</span> <span class="hljs-attr">---</span>
<span class="hljs-attr">write</span> <span class="hljs-attr">docs</span>/<span class="hljs-attr">feature-plans</span>/<span class="hljs-attr">INBOX.md</span> &lt;&lt;'<span class="hljs-attr">MD</span>'
# <span class="hljs-attr">Feature</span> <span class="hljs-attr">Plans</span> <span class="hljs-attr">Inbox</span>
<span class="hljs-attr">Latest</span> <span class="hljs-attr">planning</span> <span class="hljs-attr">updates</span> <span class="hljs-attr">appear</span> <span class="hljs-attr">here.</span>
<span class="hljs-attr">MD</span>

<span class="hljs-attr">write</span> <span class="hljs-attr">docs</span>/<span class="hljs-attr">feature-plans</span>/<span class="hljs-attr">PRD_TEMPLATE.md</span> &lt;&lt;'<span class="hljs-attr">MD</span>'
# <span class="hljs-attr">PRD:</span> {{<span class="hljs-attr">FEATURE_NAME</span>}} ({{<span class="hljs-attr">PROJECT_NAME</span>}})
## <span class="hljs-attr">Context</span>
## <span class="hljs-attr">Goals</span>
## <span class="hljs-attr">Non-Goals</span>
## <span class="hljs-attr">Requirements</span>
## <span class="hljs-attr">Open</span> <span class="hljs-attr">Questions</span> (<span class="hljs-attr">Q</span>&amp;<span class="hljs-attr">A</span>)
## <span class="hljs-attr">User</span> <span class="hljs-attr">Stories</span>
## <span class="hljs-attr">Acceptance</span> <span class="hljs-attr">Criteria</span>
## <span class="hljs-attr">Risks</span>
## <span class="hljs-attr">Metrics</span>
## <span class="hljs-attr">Rollout</span> <span class="hljs-attr">Plan</span>
## <span class="hljs-attr">Next</span> <span class="hljs-attr">Steps</span>
## <span class="hljs-attr">Changelog</span>
<span class="hljs-attr">-</span> {{<span class="hljs-attr">DATE_ISO</span>}} – <span class="hljs-attr">Initial</span> <span class="hljs-attr">draft</span> <span class="hljs-attr">created.</span>
<span class="hljs-attr">MD</span>

<span class="hljs-attr">echo</span> "✅ <span class="hljs-attr">Bootstrap</span> <span class="hljs-attr">complete.</span>"
<span class="hljs-attr">echo</span> "<span class="hljs-attr">Next:</span>"
<span class="hljs-attr">echo</span> "  <span class="hljs-attr">1</span>) <span class="hljs-attr">Ensure</span> <span class="hljs-attr">jq</span> + <span class="hljs-attr">python3</span> <span class="hljs-attr">are</span> <span class="hljs-attr">installed</span> (<span class="hljs-attr">macOS:</span> <span class="hljs-attr">brew</span> <span class="hljs-attr">install</span> <span class="hljs-attr">jq</span> <span class="hljs-attr">python</span>)"
<span class="hljs-attr">echo</span> "  <span class="hljs-attr">2</span>) <span class="hljs-attr">chmod</span> +<span class="hljs-attr">x</span> <span class="hljs-attr">.claude</span>/<span class="hljs-attr">hooks</span>/*<span class="hljs-attr">.sh</span>"
<span class="hljs-attr">echo</span> "  <span class="hljs-attr">3</span>) <span class="hljs-attr">.</span>/<span class="hljs-attr">scripts</span>/<span class="hljs-attr">render_templates.py</span> <span class="hljs-attr">ops</span>/<span class="hljs-attr">inputs</span>/<span class="hljs-attr">current.json</span>"
<span class="hljs-attr">echo</span> "  <span class="hljs-attr">4</span>) <span class="hljs-attr">Open</span> <span class="hljs-attr">Claude</span> <span class="hljs-attr">Code</span> <span class="hljs-attr">sessions:</span> <span class="hljs-attr">master-agent</span>, <span class="hljs-attr">feature-planner</span>, <span class="hljs-attr">main-dev</span>"</span></span></span>
</code></pre>
<p>Make it executable and run it:</p>
<pre><code class="lang-markdown">chmod +x bootstrap.sh
./bootstrap.sh
</code></pre>
<p>You should see the ✅ “Bootstrap complete” lines.</p>
<hr />
<h2 id="heading-step-2-render-the-agents-from-inputs">Step 2 — Render the agents from inputs</h2>
<p>This stamps the templates with your inputs:</p>
<pre><code class="lang-markdown"><span class="hljs-section"># If needed, install prerequisites</span>
<span class="hljs-section"># macOS: brew install jq python</span>
<span class="hljs-section"># Linux: sudo apt-get install -y jq python3</span>

chmod +x .claude/hooks/<span class="hljs-emphasis">*.sh
./scripts/render_templates.py ops/inputs/current.json</span>
</code></pre>
<p>This generates:</p>
<pre><code class="lang-markdown">.claude/agents/master-agent.md
.claude/agents/feature-planner.md
.claude/agents/main-dev.md
docs/feature-plans/PRD<span class="hljs-emphasis">_TEMPLATE.md (placeholders filled)</span>
</code></pre>
<hr />
<h2 id="heading-step-3-open-3-parallel-sessions-in-claude-code">Step 3 — Open 3 parallel sessions in Claude Code</h2>
<blockquote>
<p>Open the <strong>same project</strong> in Claude Code, but create <strong>three chats</strong> (or windows) and select a different agent in each via <code>/agents</code>.</p>
</blockquote>
<h3 id="heading-session-a-main-dev-coding">Session A — <code>main-dev</code> (coding)</h3>
<p><strong>Prompt:</strong></p>
<pre><code class="lang-markdown">Before coding, read /docs/feature-plans/INBOX.md (last 15 lines) and the latest PRD for “SaaS Boilerplate MVP”.
Summarize acceptance criteria that impact code and output a 5-item commit checklist.
Wait for my confirmation before touching /src.
</code></pre>
<h3 id="heading-session-b-feature-planner-planning">Session B — <code>feature-planner</code> (planning)</h3>
<p><strong>Prompt:</strong></p>
<pre><code class="lang-markdown">Plan “SaaS Boilerplate MVP”.

1) Ask me 7 brief scoping questions.
2) Copy /docs/feature-plans/PRD<span class="hljs-emphasis">_TEMPLATE.md to:
/docs/feature-plans/PLN_</span>saas-boilerplate-mvp<span class="hljs-emphasis">_{{today}}.md
3) Fill all sections (Context, Goals/Non-Goals, Requirements, Open Questions (Q&amp;A),
   User Stories, Acceptance Criteria, Risks, Metrics, Rollout Plan, Next Steps, Changelog).
4) Append a timestamped entry to Changelog.
5) Return the PRD path and 5 bullets (highlights, blockers, next steps).
6) Write only under /docs/feature-plans/.</span>
</code></pre>
<blockquote>
<p>When the planner writes, the <strong>post-hook</strong> will append a line to <code>docs/feature-plans/</code><a target="_blank" href="http://INBOX.md"><code>INBOX.md</code></a> automatically.</p>
</blockquote>
<h3 id="heading-session-m-master-agent-orchestrator">Session M — <code>master-agent</code> (orchestrator)</h3>
<p><strong>Prompt:</strong></p>
<pre><code class="lang-markdown">Read:
<span class="hljs-bullet">-</span> /ops/agent-state.json
<span class="hljs-bullet">-</span> last 50 lines of /ops/events.log
<span class="hljs-bullet">-</span> last 20 lines of /docs/feature-plans/INBOX.md

Then:
1) Update task statuses if there’s new evidence (new PRD, new branch, QA report).
2) Detect stale tasks (&gt;24h); append "Nudge" to /ops/events.log for any.
3) Merge /ops/agent-state.json.
4) Append a “Standup” summary line to /ops/events.log.

Return a 7-bullet executive summary with links (PRD path, branch, QA report).
</code></pre>
<hr />
<h2 id="heading-step-4-sanity-checks">Step 4 — Sanity Checks</h2>
<ul>
<li><p><strong>Planner</strong> writes <code>docs/feature-plans/PLN_saas-boilerplate-mvp_&lt;DATE&gt;.md</code>.</p>
</li>
<li><p><strong>INBOX</strong> gets a new line, e.g.<br />  <code>- [2025-08-23T..Z] feature-planner updated: docs/feature-plans/PLN_saas-boilerplate-mvp_</code><a target="_blank" href="http://20250823.md"><code>20250823.md</code></a></p>
</li>
<li><p><strong>Master</strong> reads INBOX + <code>/ops/events.log</code>, updates <code>/ops/agent-state.json</code>, appends a Standup.</p>
</li>
<li><p><strong>Main-dev</strong> summarizes acceptance criteria and gives a 5-item commit checklist (and will only write to <code>/src</code> and <code>/tests</code>).</p>
</li>
</ul>
<hr />
<h2 id="heading-troubleshooting">Troubleshooting</h2>
<ul>
<li><p><strong>Agents don’t appear in</strong> <code>/agents</code> menu<br />  Run the renderer again:<br />  <code>./scripts/render_</code><a target="_blank" href="http://templates.py"><code>templates.py</code></a> <code>ops/inputs/current.json</code></p>
</li>
<li><p><strong>Writes blocked unexpectedly</strong><br />  That’s the guardrail working.</p>
<ul>
<li><p><code>feature-planner</code> → only <code>/docs/feature-plans/</code></p>
</li>
<li><p><code>main-dev</code> → only <code>/src</code> and <code>/tests</code><br />  Ensure hooks are executable:<br />  <code>chmod +x .claude/hooks/*.sh</code></p>
</li>
</ul>
</li>
<li><p><strong>INBOX doesn’t update</strong><br />  Check <code>jq</code> is installed and <a target="_blank" href="http://posttooluse.sh"><code>posttooluse.sh</code></a> is executable.<br />  Confirm <code>ops/policy.json</code> includes <code>"inboxNotifyAgents": ["feature-planner"]</code>.</p>
</li>
</ul>
<hr />
<h2 id="heading-reusing-for-any-project">Reusing for Any Project</h2>
<p>Edit <code>ops/inputs/current.json</code>:</p>
<pre><code class="lang-markdown">{
  "PROJECT<span class="hljs-emphasis">_NAME": "Your Project",
  "FEATURE_</span>NAME": "Your Feature",
  "FEATURE<span class="hljs-emphasis">_SLUG": "your-feature-slug",
  "DATE": "20250823",
  "DATE_</span>ISO": "2025-08-23T00:00:00Z"
}
</code></pre>
<p>Re-render:</p>
<pre><code class="lang-markdown">./scripts/render<span class="hljs-emphasis">_templates.py ops/inputs/current.json</span>
</code></pre>
<p>You’ve now retargeted the same agents to a brand-new idea with zero code changes.</p>
<hr />
<h2 id="heading-why-this-pattern-works">Why This Pattern Works</h2>
<ul>
<li><p><strong>Parallelism</strong>: planning continues while coding ships.</p>
</li>
<li><p><strong>Safety</strong>: hooks enforce hard boundaries (no accidental edits).</p>
</li>
<li><p><strong>Awareness</strong>: <a target="_blank" href="http://INBOX.md"><code>INBOX.md</code></a> and <code>/ops/events.log</code> are lightweight “message bus” files agents share.</p>
</li>
<li><p><strong>Portability</strong>: swap inputs and reuse the same scaffolding for any project.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Introducing DataPages.io 📊 🚀]]></title><description><![CDATA[From CSVs to Beautiful Web Pages: Why I Built DataPages.io (and What’s Next)
Like many indie hackers, I’ve wrestled with the simple problem of sharing data.
Too often, we pass around messy spreadsheets in emails, Slack, or Google Drive, only to have ...]]></description><link>https://rcmisk.com/introducing-datapagesio</link><guid isPermaLink="true">https://rcmisk.com/introducing-datapagesio</guid><category><![CDATA[Build In Public]]></category><category><![CDATA[No Code]]></category><category><![CDATA[data]]></category><dc:creator><![CDATA[rcmisk]]></dc:creator><pubDate>Wed, 20 Aug 2025 02:13:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755655954945/31a5a526-519a-454d-9317-0f02db9859e0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-from-csvs-to-beautiful-web-pages-why-i-built-datapagesio-and-whats-next">From CSVs to Beautiful Web Pages: Why I Built DataPages.io (and What’s Next)</h1>
<p>Like many indie hackers, I’ve wrestled with the simple problem of <strong>sharing data</strong>.</p>
<p>Too often, we pass around messy spreadsheets in emails, Slack, or Google Drive, only to have them get lost, versioned to death, or become unreadable.</p>
<p>So I built <a target="_blank" href="https://datapages.io"><strong>DataPages.io</strong></a> — a simple way to turn <strong>any CSV file into a clean, searchable, sortable web page in seconds.</strong> No code required.</p>
<hr />
<h2 id="heading-how-datapages-works">🚀 How DataPages Works</h2>
<ol>
<li><p><strong>Upload your CSV</strong> (drag-and-drop simple).</p>
</li>
<li><p><strong>Pick your subdomain</strong> (e.g. <code>yourdata.datapages.io</code>).</p>
</li>
<li><p><strong>Choose visibility</strong> — Public, Semi-Public, or Private.</p>
</li>
<li><p><strong>Share your live DataPage</strong> instantly.</p>
</li>
</ol>
<p>That’s it. Your messy spreadsheet becomes a fast, accessible, mobile-friendly data page.</p>
<hr />
<h2 id="heading-why-this-matters">💡 Why This Matters</h2>
<ul>
<li><p><strong>Stop sending spreadsheets around</strong> — just share a link.</p>
</li>
<li><p><strong>Data stays live &amp; easy to explore</strong> — searchable, sortable, responsive.</p>
</li>
<li><p><strong>Privacy first</strong> — control who can access your data (with Google Auth or public link).</p>
</li>
</ul>
<p>I wanted something <strong>lightweight and fast</strong>, with none of the friction of “sign up for Airtable” or “export to Notion.” Just: upload → link → done.</p>
<hr />
<h2 id="heading-the-challenge-early-users-no-usage-yet">⚡ The Challenge: Early Users, No Usage (Yet!)</h2>
<p>I launched a few weeks ago and already have <strong>a handful of signups</strong> 🎉.</p>
<p>But… many haven’t actually used the product yet. That’s my next puzzle.</p>
<p>Here’s what I’ve realized about <strong>why</strong>:</p>
<ul>
<li><p>The <strong>value prop wasn’t clear enough</strong> (why use this over spreadsheets?).</p>
</li>
<li><p>The <strong>onboarding lacked inspiration</strong> — if you don’t have a dataset ready, you stall out.</p>
</li>
<li><p>I hadn’t provided <strong>real-life examples</strong> to spark ideas.</p>
</li>
</ul>
<hr />
<h2 id="heading-what-im-fixing-next">🔨 What I’m Fixing Next</h2>
<p>I’m treating this as a build-in-public experiment, so here’s what’s coming next:</p>
<h3 id="heading-1-live-examples">1. <strong>Live Examples</strong></h3>
<p>So you can instantly see what’s possible, I’m adding pre-made DataPages like:</p>
<ul>
<li><p>A list of top indie hacker tools 💻</p>
</li>
<li><p>A sample budget tracker 💰</p>
</li>
<li><p>A conference schedule 🗓️</p>
</li>
</ul>
<h3 id="heading-2-sample-csvs">2. <strong>Sample CSVs</strong></h3>
<p>Not sure what to upload? I’ll give you simple CSVs (like <code>countries.csv</code> or <code>monthly_sales.csv</code>) to try instantly.</p>
<h3 id="heading-3-better-onboarding">3. <strong>Better Onboarding</strong></h3>
<p>Instead of a blank dashboard, new users will see a <strong>“Drag your CSV here”</strong> prompt and one-click example DataPages to explore.</p>
<h3 id="heading-4-email-nudges">4. <strong>Email Nudges</strong></h3>
<p>If you signed up but didn’t upload, expect a friendly nudge:<br /><em>“Upload any CSV to unlock your first live DataPage — it only takes 10 seconds.”</em></p>
<h3 id="heading-5-sharing-micro-wins">5. <strong>Sharing Micro-Wins</strong></h3>
<p>I’ll keep posting screenshots, short demos, and small wins here on X/Twitter to show real-world use cases.</p>
<hr />
<h2 id="heading-why-im-sharing-this">🔮 Why I’m Sharing This</h2>
<p>I believe tools like this grow best with community feedback. If you’re into <strong>data, indie hacking, or no-code</strong>, I’d love for you to:</p>
<ul>
<li><p>Try creating your first DataPage (takes less than a minute).</p>
</li>
<li><p>Share feedback: What’s confusing? What’s exciting?</p>
</li>
<li><p>Tell me how <em>you</em> might use this in your projects.</p>
</li>
</ul>
<hr />
<p>👉 <a target="_blank" href="https://datapages.io">Check it out at datapages.io</a></p>
<p>And if you’ve ever thought <em>“I wish I could just share this spreadsheet without all the hassle”</em> — this might be for you.</p>
<p>Thanks for following along. Excited to see what the #buildinpublic community does with it 🙌</p>
]]></content:encoded></item><item><title><![CDATA[How to Sync Context Across Separate Claude Code Sub-Agent Sessions (Step-by-Step Guide)]]></title><description><![CDATA[Running multiple Claude Code sub-agent sessions in parallel (for example, a dedicated debugger and a code-reviewer) is powerful for focus and productivity. But there’s one problem: sessions don’t share context by default.
This guide shows you how to ...]]></description><link>https://rcmisk.com/how-to-sync-context-across-separate-claude-code-sub-agent-sessions-step-by-step-guide</link><guid isPermaLink="true">https://rcmisk.com/how-to-sync-context-across-separate-claude-code-sub-agent-sessions-step-by-step-guide</guid><category><![CDATA[claude.ai]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[code]]></category><category><![CDATA[Developer Tools]]></category><category><![CDATA[AI]]></category><category><![CDATA[#ai-tools]]></category><dc:creator><![CDATA[rcmisk]]></dc:creator><pubDate>Mon, 18 Aug 2025 18:30:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/hvSr_CVecVI/upload/e185cc09bbb6572986cdad45ae8b3ffc.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Running multiple <strong>Claude Code sub-agent sessions</strong> in parallel (for example, a dedicated <code>debugger</code> and a <code>code-reviewer</code>) is powerful for focus and productivity. But there’s one problem: <strong>sessions don’t share context by default</strong>.</p>
<p>This guide shows you how to <strong>sync context across separate sub-agent sessions</strong>. You’ll learn:</p>
<ul>
<li><p>Four different strategies for context sharing</p>
</li>
<li><p>When to use repo files vs. databases vs. MCP integrations</p>
</li>
<li><p>A <strong>step-by-step, automated SQLite context sync tool</strong> you can install in minutes</p>
</li>
</ul>
<p>By the end, you’ll have a <strong>repeatable workflow</strong> for sharing notes and state across multiple Claude Code sessions—no more manual copy/paste.</p>
<hr />
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#why-context-sync-matters">Why context sync matters</a></p>
</li>
<li><p><a class="post-section-overview" href="#4-proven-strategies-for-context-sharing">4 proven strategies for context sharing</a></p>
</li>
<li><p><a class="post-section-overview" href="#step-by-step-automating-context-sync-with-sqlite">Step-by-step: Automating context sync with SQLite</a></p>
</li>
<li><p><a class="post-section-overview" href="#example-workflow-with-parallel-sub-agents">Example workflow with parallel sub-agents</a></p>
</li>
<li><p><a class="post-section-overview" href="#troubleshooting--best-practices">Troubleshooting &amp; best practices</a></p>
</li>
<li><p><a class="post-section-overview" href="#next-steps-hooks-for-automatic-syncing">Next steps: Hooks for automatic syncing</a></p>
</li>
</ol>
<hr />
<h2 id="heading-why-context-sync-matters">Why context sync matters</h2>
<p>When you run sub-agents like a <code>debugger</code>, <code>code-reviewer</code>, or <code>doc-writer</code> in <strong>separate Claude Code sessions</strong>, each one has its own context window. Without a sync strategy, you risk:</p>
<ul>
<li><p>Losing critical debugging notes</p>
</li>
<li><p>Duplicating work across sessions</p>
</li>
<li><p>Forgetting what other agents have already solved</p>
</li>
</ul>
<p><strong>Context syncing</strong> ensures that your multi-agent workflow stays coordinated and efficient.</p>
<hr />
<h2 id="heading-4-proven-strategies-for-context-sharing">4 Proven Strategies for Context Sharing</h2>
<h3 id="heading-1-markdown-notes-in-repo">1. Markdown Notes in Repo</h3>
<ul>
<li><p>Store shared notes in <code>.claude/context/*.md</code></p>
</li>
<li><p>Simple, human-readable, and version-controlled</p>
</li>
<li><p>✅ Easy to set up, but can grow noisy</p>
</li>
</ul>
<h3 id="heading-2-sqlite-scratchpad-recommended">2. SQLite Scratchpad (recommended)</h3>
<ul>
<li><p>Structured, queryable context DB</p>
</li>
<li><p>Easy to automate with a <code>ctx</code> CLI tool</p>
</li>
<li><p>✅ Scales better than raw markdown</p>
</li>
</ul>
<h3 id="heading-3-mcp-backed-stores-redis-external-db">3. MCP-Backed Stores (Redis, external DB)</h3>
<ul>
<li><p>Centralized context accessible via <strong>Model Context Protocol (MCP)</strong> integrations</p>
</li>
<li><p>Best for teams or multi-machine setups</p>
</li>
<li><p>⚠️ Requires extra setup and permissions</p>
</li>
</ul>
<h3 id="heading-4-regenerate-on-demand">4. Regenerate on Demand</h3>
<ul>
<li><p>Each sub-agent recomputes its needed context from git diffs, logs, or tests</p>
</li>
<li><p>✅ Zero coupling</p>
</li>
<li><p>⚠️ Doesn’t share rationales or decisions</p>
</li>
</ul>
<hr />
<h2 id="heading-step-by-step-automating-context-sync-with-sqlite">Step-by-Step: Automating Context Sync with SQLite</h2>
<p>This section walks you through building a <strong>drop-in context scratchpad</strong> with SQLite that any Claude Code sub-agent session can read/write.</p>
<h3 id="heading-0-scaffold">0) Scaffold</h3>
<pre><code class="lang-markdown">mkdir -p tools .claude/context
python -m venv .venv &amp;&amp; source .venv/bin/activate
pip install --upgrade pip
pip install tabulate
</code></pre>
<h3 id="heading-1-add-the-cli-toolsctxpy">1) Add the CLI (<code>tools/ctx.py</code>)</h3>
<pre><code class="lang-markdown"><span class="hljs-section">#!/usr/bin/env python3</span>
import argparse, sqlite3, os, time
from tabulate import tabulate

DB<span class="hljs-emphasis">_PATH = os.environ.get("CTX_</span>DB", ".claude/context/context.db")

DDL = """
CREATE TABLE IF NOT EXISTS entries (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  ts INTEGER NOT NULL,
  agent TEXT NOT NULL,
  session TEXT,
  topic TEXT,
  summary TEXT,
  refs TEXT
);
"""

def connect():
<span class="hljs-code">    os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
    conn = sqlite3.connect(DB_PATH)
    conn.executescript(DDL)
    return conn
</span>
def cmd<span class="hljs-emphasis">_add(args):
    conn = connect()
    with conn:
        conn.execute(
            "INSERT INTO entries(ts, agent, session, topic, summary, refs) VALUES(?,?,?,?,?,?)",
            (int(time.time()), args.agent, args.session, args.topic, args.summary, args.refs)
        )
    print("ok: added")

def cmd_</span>read(args):
<span class="hljs-code">    conn = connect()
    q = "SELECT ts, agent, session, topic, summary, refs FROM entries ORDER BY ts DESC LIMIT ?"
    rows = conn.execute(q, (args.limit,)).fetchall()
    out = []
    for ts,agent,session,topic,summary,refs in rows:
        out.append([time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ts)), agent, session or "", topic or "", (summary or "")[:240], refs or ""])
    print(tabulate(out, headers=["time","agent","session","topic","summary","refs"], tablefmt="github"))
</span>
def main():
<span class="hljs-code">    ap = argparse.ArgumentParser()
    sub = ap.add_subparsers(dest="cmd", required=True)
</span>
<span class="hljs-code">    ap_add = sub.add_parser("add")
    ap_add.add_argument("--agent", required=True)
    ap_add.add_argument("--session", default="")
    ap_add.add_argument("--topic", default="")
    ap_add.add_argument("--summary", required=True)
    ap_add.add_argument("--refs", default="")
    ap_add.set_defaults(func=cmd_add)
</span>
<span class="hljs-code">    ap_read = sub.add_parser("read")
    ap_read.add_argument("--limit", type=int, default=50)
    ap_read.set_defaults(func=cmd_read)
</span>
<span class="hljs-code">    args = ap.parse_args()
    args.func(args)
</span>
if <span class="hljs-strong">__name__</span> == "<span class="hljs-strong">__main__</span>":
<span class="hljs-code">    main()</span>
</code></pre>
<p>Make it executable:</p>
<pre><code class="lang-markdown">chmod +x tools/ctx.py
</code></pre>
<h3 id="heading-2-use-it-in-sub-agent-prompts">2) Use it in Sub-Agent Prompts</h3>
<p>Example <code>debugger.md</code>:</p>
<pre><code class="lang-markdown">---
name: debugger
description: Debugging specialist for errors and test failures. Auto-syncs context.
<span class="hljs-section">tools: Read, Write, Grep, Glob, Bash
---</span>

<span class="hljs-section">## Context Sync (before work)</span>
<span class="hljs-bullet">-</span> Run: <span class="hljs-code">`Bash -&gt; python tools/ctx.py read --limit 20`</span>
<span class="hljs-bullet">-</span> Integrate relevant issues from other agents.

<span class="hljs-section">## Task Procedure</span>
1) Capture error/stack trace
2) Localize failure region
3) Apply smallest safe fix
4) Verify with tests

<span class="hljs-section">## Context Sync (after work)</span>
<span class="hljs-bullet">-</span> Summarize root cause, fix, verification
<span class="hljs-bullet">-</span> Run: <span class="hljs-code">`Bash -&gt; python tools/ctx.py add --agent debugger --session "&lt;label&gt;" --topic "&lt;topic&gt;" --summary "&lt;summary&gt;" --refs "&lt;files&gt;"`</span>
</code></pre>
<h3 id="heading-3-example-session-usage">3) Example Session Usage</h3>
<ul>
<li><p>Start <code>code-reviewer</code> session: log issues with <code>ctx add</code></p>
</li>
<li><p>Start <code>debugger</code> session: load reviewer notes with <code>ctx read</code>, then append fix summary</p>
</li>
<li><p>Start <code>doc-writer</code> session: load both, generate release notes</p>
</li>
</ul>
<p>Now, all three sessions share a <strong>durable context database</strong>.</p>
<hr />
<h2 id="heading-example-workflow-with-parallel-sub-agents">Example Workflow with Parallel Sub-Agents</h2>
<p><strong>Session A (code-reviewer)</strong></p>
<ul>
<li><p>Review commit</p>
</li>
<li><p>Log summary with <code>ctx add</code></p>
</li>
</ul>
<p><strong>Session B (debugger)</strong></p>
<ul>
<li><p>Load reviewer context with <code>ctx read</code></p>
</li>
<li><p>Apply fix</p>
</li>
<li><p>Append summary with <code>ctx add</code></p>
</li>
</ul>
<p><strong>Session C (doc-writer)</strong></p>
<ul>
<li><p>Load reviewer + debugger context</p>
</li>
<li><p>Update documentation</p>
</li>
<li><p>Save context</p>
</li>
</ul>
<p>This pattern creates a <strong>multi-agent workflow</strong> with shared state across sessions.</p>
<hr />
<h2 id="heading-troubleshooting-amp-best-practices">Troubleshooting &amp; Best Practices</h2>
<ul>
<li><p><strong>Nothing appears in</strong> <code>read</code>? Check your DB path or limit flag.</p>
</li>
<li><p><strong>Label consistently</strong>: Use session IDs like <code>CR-12</code>, <code>DBG-45</code>.</p>
</li>
<li><p><strong>Avoid logging secrets</strong>: Keep <code>.claude/context/</code> in <code>.gitignore</code>.</p>
</li>
<li><p><strong>Summarize, don’t dump</strong>: Add concise notes, not full logs.</p>
</li>
</ul>
<hr />
<h2 id="heading-next-steps-hooks-for-automatic-syncing">Next Steps: Hooks for Automatic Syncing</h2>
<p>Right now, agents politely run <code>ctx read</code> at the start and <code>ctx add</code> at the end. In the next article, we’ll explore <strong>Claude Code Hooks</strong> to enforce this automatically—for example:</p>
<ul>
<li><p>Run <code>ctx read</code> <em>before every task</em> (<code>PreTask</code> hook)</p>
</li>
<li><p>Run <code>ctx add</code> <em>after every task</em> (<code>PostTask</code> hook)</p>
</li>
</ul>
<p>This ensures every sub-agent session consistently loads and writes context—even if you forget.</p>
<hr />
<h2 id="heading-internal-resource">Internal Resource</h2>
<p>👉 If you’re new to sub-agents, start with our <a target="_blank" href="https://rcmisk.com/mastering-claude-code-sub-agents-a-step-by-step-guide">Beginner’s Guide to Claude Code Sub-Agents</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Mastering Claude Code Sub-Agents: A Step-by-Step Guide]]></title><description><![CDATA[Claude Code - Sub-Agents - with Examples, Tools, Workflow Integration, and Session Strategies
Claude Code’s sub-agents are like specialized teammates you can summon inside your coding environment. Instead of asking your main assistant to do everythin...]]></description><link>https://rcmisk.com/mastering-claude-code-sub-agents-a-step-by-step-guide</link><guid isPermaLink="true">https://rcmisk.com/mastering-claude-code-sub-agents-a-step-by-step-guide</guid><category><![CDATA[claude.ai]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[#ai-tools]]></category><category><![CDATA[coding]]></category><dc:creator><![CDATA[rcmisk]]></dc:creator><pubDate>Mon, 18 Aug 2025 17:52:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/OI1ToozsKBw/upload/1488a18ddff03c4126cc62a0ec1680dd.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-claude-code-sub-agents-with-examples-tools-workflow-integration-and-session-strategies">Claude Code - Sub-Agents - with Examples, Tools, Workflow Integration, and Session Strategies</h1>
<p>Claude Code’s <strong>sub-agents</strong> are like specialized teammates you can summon inside your coding environment. Instead of asking your main assistant to do <em>everything</em> (debugging, reviewing, writing docs, analyzing data), you can spin up focused helpers with their own roles, permissions, and context.</p>
<p>This guide gives you the complete playbook:</p>
<ul>
<li><p>What sub-agents are</p>
</li>
<li><p>When to use them (and when not to)</p>
</li>
<li><p>How to create them (UI + files)</p>
</li>
<li><p>Copy-paste examples</p>
</li>
<li><p>The <strong>full tool list</strong> + cheat sheet</p>
</li>
<li><p>How to <strong>incorporate them into your workflow while the main agent is running</strong></p>
</li>
<li><p>When to use <strong>inline orchestration vs separate sessions</strong></p>
</li>
</ul>
<hr />
<h2 id="heading-what-are-sub-agents">What are sub-agents?</h2>
<p>Sub-agents are scoped assistants with:</p>
<ul>
<li><p>Their own <strong>system prompt</strong> (role + rules)</p>
</li>
<li><p>Their own <strong>tool permissions</strong> (what they’re allowed to do)</p>
</li>
<li><p>Their own <strong>context window</strong> (fresh memory separate from your main thread)</p>
</li>
</ul>
<p>You can either:</p>
<ul>
<li><p><strong>Invoke them explicitly</strong> by name (<code>use the code-reviewer subagent…</code>)</p>
</li>
<li><p>Or let Claude <strong>auto-delegate</strong> if your description matches their role</p>
</li>
</ul>
<p>This separation keeps your main chat clean and makes tasks safer, faster, and more reliable.</p>
<hr />
<h2 id="heading-when-and-when-not-to-use-sub-agents">When (and when not) to use sub-agents</h2>
<p>✅ <strong>Great fits</strong></p>
<ul>
<li><p>Multi-step tasks (e.g., <em>research → write → review</em>)</p>
</li>
<li><p>Guardrails (a reviewer that enforces security standards)</p>
</li>
<li><p>Specialized expertise (SQL queries, doc writing, profiling)</p>
</li>
<li><p>Extensible workflows (swap in a TweetWriter without touching research logic)</p>
</li>
</ul>
<p>❌ <strong>Overkill</strong></p>
<ul>
<li><p>One-off questions</p>
</li>
<li><p>Tiny edits or explanations that don’t need a dedicated context</p>
</li>
</ul>
<hr />
<h2 id="heading-how-to-create-sub-agents">How to create sub-agents</h2>
<h3 id="heading-option-a-with-the-agents-ui">Option A — With the <code>/agents</code> UI</h3>
<ol>
<li><p>Run <code>/agents</code> in Claude Code</p>
</li>
<li><p>Click <strong>Create New Agent</strong></p>
</li>
<li><p>Fill in:</p>
<ul>
<li><p><strong>Name</strong></p>
</li>
<li><p><strong>Description</strong> (when/why to use it — keep action-oriented)</p>
</li>
<li><p><strong>Tools</strong> (leave blank to inherit all)</p>
</li>
</ul>
</li>
<li><p>Save. Edit the system prompt if needed.</p>
</li>
</ol>
<h3 id="heading-option-b-markdown-files">Option B — Markdown files</h3>
<p>Sub-agents live in:</p>
<ul>
<li><p>Project scope → <code>.claude/agents/</code></p>
</li>
<li><p>User scope → <code>~/.claude/agents/</code></p>
</li>
</ul>
<p>Example:</p>
<pre><code class="lang-markdown">---
name: code-reviewer
description: Focused code review after each commit.
<span class="hljs-section">tools: Read, Grep, Glob, Bash
---</span>

You are a senior reviewer. Always inspect diffs and enforce quality, security, and style.
</code></pre>
<hr />
<h2 id="heading-example-sub-agents-drop-into-claudeagents">Example sub-agents (drop into <code>.claude/agents/</code>)</h2>
<p><em>(…examples for Code Reviewer, Debugger, Data Scientist, Reddit Researcher — unchanged here for brevity but included in full earlier…)</em></p>
<hr />
<h2 id="heading-full-tool-list">Full tool list</h2>
<p>Sub-agents can be given these tools:</p>
<ul>
<li><p><strong>Read</strong> → Read files</p>
</li>
<li><p><strong>Write</strong> → Create/edit files</p>
</li>
<li><p><strong>Grep</strong> → Search inside files</p>
</li>
<li><p><strong>Glob</strong> → Match file patterns (<code>*.py</code>, <code>src/**</code>)</p>
</li>
<li><p><strong>Bash</strong> → Run shell commands/tests</p>
</li>
<li><p><strong>MCP tools</strong> → External integrations (DBs, APIs, cloud services)</p>
</li>
</ul>
<p>⚠️ If you omit the <code>tools:</code> line, the agent inherits <strong>all tools</strong> available in your main session.</p>
<hr />
<h2 id="heading-tool-cheat-sheet-by-agent-type">Tool cheat sheet (by agent type)</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Sub-Agent Type</td><td>Recommended Tools</td><td>Why</td></tr>
</thead>
<tbody>
<tr>
<td>Code Reviewer</td><td><code>Read, Grep, Glob, Bash</code></td><td>Inspects code only</td></tr>
<tr>
<td>Debugger</td><td><code>Read, Write, Grep, Glob, Bash</code></td><td>Fixes + tests</td></tr>
<tr>
<td>Test Runner</td><td><code>Read, Bash</code></td><td>Runs test suites</td></tr>
<tr>
<td>Doc Writer</td><td><code>Read, Write</code></td><td>Updates docs</td></tr>
<tr>
<td>Data Scientist</td><td><code>Read, Bash</code> (+MCP DB)</td><td>Runs queries</td></tr>
<tr>
<td>Profiler</td><td><code>Read, Bash, Grep</code></td><td>Runs profiling tools</td></tr>
<tr>
<td>Security Auditor</td><td><code>Read, Grep, Glob</code></td><td>Scans only</td></tr>
<tr>
<td>Content Writer</td><td><code>Read, Write</code></td><td>Creates text artifacts</td></tr>
<tr>
<td>Researcher</td><td><code>Bash</code> or MCP only</td><td>Sandbox</td></tr>
<tr>
<td>General Assistant</td><td>(no <code>tools:</code> field)</td><td>Full inherited toolset</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-incorporating-sub-agents-into-your-workflow-while-main-agent-is-running">Incorporating sub-agents into your workflow (while main agent is running)</h2>
<p><em>(…section covering inline invocation, auto-delegation, chaining,</em> <code>/agents</code>, Hooks, permissions, MCP, and workflow playbooks — as written in the prior draft…)</p>
<hr />
<h2 id="heading-using-sub-agents-in-separate-sessions">Using sub-agents in <strong>separate sessions</strong></h2>
<p>By default, sub-agents run <strong>inside your main session</strong>, returning results inline. But you can also spin up <strong>dedicated sessions</strong> where a sub-agent is the “main persona.”</p>
<h3 id="heading-inline-orchestration-default">Inline orchestration (default)</h3>
<ul>
<li><p>Stay in one thread</p>
</li>
<li><p>Call <code>code-reviewer</code>, <code>debugger</code>, etc. inline</p>
</li>
<li><p>Results are merged into your main chat</p>
</li>
<li><p>Great for continuity, orchestration, and quick workflows</p>
</li>
</ul>
<h3 id="heading-separate-sessions">Separate sessions</h3>
<ul>
<li><p>Open a new Claude Code session and choose a sub-agent directly</p>
</li>
<li><p>That session runs <em>only</em> with that agent’s prompt + tool permissions</p>
</li>
<li><p>Benefits:</p>
<ul>
<li><p><strong>Focus</strong>: a long-lived debugging or research workspace without cluttering your main thread</p>
</li>
<li><p><strong>Parallel work</strong>: keep multiple tabs open (e.g., <code>data-scientist</code> running queries while <code>code-reviewer</code> runs in another)</p>
</li>
<li><p><strong>Isolation</strong>: huge contexts (like Reddit scraping notes) don’t pollute your main chat</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-caveats">Caveats</h3>
<ul>
<li><p>Separate sessions don’t share state — you’ll need to copy/paste or sync context</p>
</li>
<li><p>Each session enforces the agent’s tool list</p>
</li>
<li><p>Edits to an agent file require restarting the session</p>
</li>
</ul>
<p><strong>Rule of thumb:</strong></p>
<ul>
<li><p>Use <strong>inline</strong> for orchestration and everyday coding</p>
</li>
<li><p>Use <strong>separate sessions</strong> for deep, focused, or long-lived specialist tasks</p>
</li>
</ul>
<hr />
<h2 id="heading-best-practices">Best practices</h2>
<ul>
<li><p>Small, focused agents &gt; one giant multi-purpose agent</p>
</li>
<li><p>Write prompts as checklists with constraints</p>
</li>
<li><p>Restrict tools to enforce safety</p>
</li>
<li><p>Version <code>.claude/agents/</code> in your repo so the team shares agents</p>
</li>
<li><p>Prefer explicit invocation for critical workflows</p>
</li>
</ul>
<hr />
<h2 id="heading-quick-starter-pack">Quick starter pack</h2>
<pre><code class="lang-markdown">mkdir -p .claude/agents
</code></pre>
<p>Add these:</p>
<ul>
<li><p><a target="_blank" href="http://code-reviewer.md"><code>code-reviewer.md</code></a></p>
</li>
<li><p><a target="_blank" href="http://debugger.md"><code>debugger.md</code></a></p>
</li>
<li><p><a target="_blank" href="http://data-scientist.md"><code>data-scientist.md</code></a></p>
</li>
<li><p><a target="_blank" href="http://reddit-researcher.md"><code>reddit-researcher.md</code></a></p>
</li>
</ul>
<p>Run <code>/agents</code> to verify.</p>
<hr />
<h2 id="heading-wrap-up">Wrap-up</h2>
<p>Sub-agents make Claude Code modular, safer, and smarter. They let your <strong>main agent stay conversational and orchestral</strong>, while specialists do the heavy lifting exactly when needed.</p>
<p>Start small: add a <code>code-reviewer</code> and a <code>debugger</code>. Once you feel the speedup, extend to data analysis, doc writing, or research — and explore running some inline, some as dedicated sessions.</p>
]]></content:encoded></item><item><title><![CDATA[Creating Custom Claude Slash Commands]]></title><description><![CDATA[To create a custom command in Claude Code, you'll need to understand how Claude Code's command system works and follow the proper configuration steps. Let me search for the most current documentation to give you accurate instructions.Based on the off...]]></description><link>https://rcmisk.com/creating-custom-claude-slash-commands</link><guid isPermaLink="true">https://rcmisk.com/creating-custom-claude-slash-commands</guid><category><![CDATA[AI]]></category><category><![CDATA[claude.ai]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[coding]]></category><dc:creator><![CDATA[rcmisk]]></dc:creator><pubDate>Sat, 16 Aug 2025 04:54:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755319953173/c3a1726f-2f7c-40f0-b679-fdea78410d65.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>To create a custom command in Claude Code, you'll need to understand how Claude Code's command system works and follow the proper configuration steps. Let me search for the most current documentation to give you accurate instructions.Based on the official documentation and best practices, here's how to create custom commands in Claude Code:</p>
<h2 id="heading-creating-custom-slash-commands"><strong>Creating Custom Slash Commands</strong></h2>
<h3 id="heading-1-command-structure-amp-location"><strong>1. Command Structure &amp; Location</strong></h3>
<p>Custom commands are stored as Markdown files in specific directories:</p>
<p><strong>Project-specific commands:</strong></p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create project commands directory</span>
mkdir -p .claude/commands

<span class="hljs-comment"># Create a command file</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"Your command prompt here"</span> &gt; .claude/commands/optimize.md
</code></pre>
<p><strong>Personal commands (across all projects):</strong></p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create personal commands directory</span>
mkdir -p ~/.claude/commands

<span class="hljs-comment"># Create a personal command</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"Your command prompt here"</span> &gt; ~/.claude/commands/security-review.md
</code></pre>
<h3 id="heading-2-basic-command-example"><strong>2. Basic Command Example</strong></h3>
<pre><code class="lang-markdown"><span class="hljs-section"># Example 1: Basic Code Review Command</span>
<span class="hljs-section"># File: .claude/commands/review.md</span>

Perform a comprehensive code review of recent changes:

<span class="hljs-bullet">1.</span> <span class="hljs-strong">**Code Quality Analysis**</span>
<span class="hljs-bullet">   -</span> Check code follows our TypeScript and React conventions
<span class="hljs-bullet">   -</span> Verify proper error handling and loading states
<span class="hljs-bullet">   -</span> Ensure accessibility standards are met

<span class="hljs-bullet">2.</span> <span class="hljs-strong">**Security &amp; Performance**</span>
<span class="hljs-bullet">   -</span> Review for security vulnerabilities
<span class="hljs-bullet">   -</span> Check for performance implications
<span class="hljs-bullet">   -</span> Validate input sanitization

<span class="hljs-bullet">3.</span> <span class="hljs-strong">**Testing &amp; Documentation**</span>
<span class="hljs-bullet">   -</span> Review test coverage for new functionality
<span class="hljs-bullet">   -</span> Confirm documentation is updated
<span class="hljs-bullet">   -</span> Check for breaking changes

<span class="hljs-bullet">4.</span> <span class="hljs-strong">**Best Practices**</span>
<span class="hljs-bullet">   -</span> Ensure consistent code style
<span class="hljs-bullet">   -</span> Verify proper component structure
<span class="hljs-bullet">   -</span> Check for code duplication

---

<span class="hljs-section"># Example 2: Command with Arguments</span>
<span class="hljs-section"># File: .claude/commands/fix-issue.md</span>

Analyze and fix GitHub issue #$ARGUMENTS following our coding standards:

<span class="hljs-bullet">1.</span> <span class="hljs-strong">**Investigation**</span>
<span class="hljs-bullet">   -</span> Use <span class="hljs-code">`gh issue view $ARGUMENTS`</span> to get issue details
<span class="hljs-bullet">   -</span> Search codebase for relevant files
<span class="hljs-bullet">   -</span> Identify root cause of the problem

<span class="hljs-bullet">2.</span> <span class="hljs-strong">**Implementation**</span>
<span class="hljs-bullet">   -</span> Implement solution addressing the root cause
<span class="hljs-bullet">   -</span> Follow our coding conventions and patterns
<span class="hljs-bullet">   -</span> Add appropriate error handling

<span class="hljs-bullet">3.</span> <span class="hljs-strong">**Testing &amp; Validation**</span>
<span class="hljs-bullet">   -</span> Write comprehensive tests for the fix
<span class="hljs-bullet">   -</span> Run existing test suite to ensure no regressions
<span class="hljs-bullet">   -</span> Verify the fix resolves the original issue

<span class="hljs-bullet">4.</span> <span class="hljs-strong">**Documentation**</span>
<span class="hljs-bullet">   -</span> Update relevant documentation
<span class="hljs-bullet">   -</span> Add code comments where necessary
<span class="hljs-bullet">   -</span> Create clear commit message

Usage: /fix-issue 123

---

<span class="hljs-section"># Example 3: Command with Pre-execution Bash Commands</span>
<span class="hljs-section"># File: .claude/commands/commit-review.md</span>

---
allowed-tools: Bash(git add:<span class="hljs-emphasis">*), Bash(git status:*</span>), Bash(git commit:<span class="hljs-emphasis">*), Bash(git diff:*</span>)
<span class="hljs-section">description: Review and commit changes with comprehensive analysis
---</span>

<span class="hljs-section">## Context</span>
<span class="hljs-bullet">-</span> Current git status: !<span class="hljs-code">`git status`</span>
<span class="hljs-bullet">-</span> Current git diff (staged and unstaged changes): !<span class="hljs-code">`git diff HEAD`</span>
<span class="hljs-bullet">-</span> Current branch: !<span class="hljs-code">`git branch --show-current`</span>
<span class="hljs-bullet">-</span> Recent commits: !<span class="hljs-code">`git log --oneline -5`</span>

<span class="hljs-section">## Review Process</span>
<span class="hljs-bullet">1.</span> <span class="hljs-strong">**Change Analysis**</span>
<span class="hljs-bullet">   -</span> Analyze all modified files for quality and consistency
<span class="hljs-bullet">   -</span> Check for potential breaking changes
<span class="hljs-bullet">   -</span> Verify all changes align with the intended feature/fix

<span class="hljs-bullet">2.</span> <span class="hljs-strong">**Testing Verification**</span>
<span class="hljs-bullet">   -</span> Ensure all tests pass before committing
<span class="hljs-bullet">   -</span> Verify new functionality has appropriate test coverage
<span class="hljs-bullet">   -</span> Check for any test-related changes needed

<span class="hljs-bullet">3.</span> <span class="hljs-strong">**Commit Creation**</span>
<span class="hljs-bullet">   -</span> Create descriptive commit message following conventional commits
<span class="hljs-bullet">   -</span> Stage appropriate files for commit
<span class="hljs-bullet">   -</span> Execute commit with proper formatting

---

<span class="hljs-section"># Example 4: Namespace Organization</span>
<span class="hljs-section"># File: .claude/commands/frontend/component.md</span>

Create a new React component following our design system:

<span class="hljs-bullet">1.</span> <span class="hljs-strong">**Component Structure**</span>
<span class="hljs-bullet">   -</span> Generate TypeScript component with proper props interface
<span class="hljs-bullet">   -</span> Include proper JSDoc documentation
<span class="hljs-bullet">   -</span> Follow our naming conventions (PascalCase)

<span class="hljs-bullet">2.</span> <span class="hljs-strong">**Styling &amp; Accessibility**</span>
<span class="hljs-bullet">   -</span> Use Tailwind CSS classes following our design tokens
<span class="hljs-bullet">   -</span> Include proper ARIA attributes
<span class="hljs-bullet">   -</span> Ensure keyboard navigation support

<span class="hljs-bullet">3.</span> <span class="hljs-strong">**Testing Setup**</span>
<span class="hljs-bullet">   -</span> Create component test file with basic render test
<span class="hljs-bullet">   -</span> Include accessibility testing with jest-axe
<span class="hljs-bullet">   -</span> Add story file for Storybook documentation

Usage: /component ButtonVariant
Creates: components/ButtonVariant.tsx, tests, and stories

---

<span class="hljs-section"># Example 5: Advanced Workflow Command</span>
<span class="hljs-section"># File: .claude/commands/deploy/prepare-release.md</span>

---
allowed-tools: Bash(<span class="hljs-emphasis">*), Git(*</span>), Node(<span class="hljs-emphasis">*)
description: Prepare complete release package with all validations
---

## Pre-Release Checklist
- Current version: !`cat package.json | grep version`
- Git status: !`git status --porcelain`
- Last release: !`git tag -l --sort=-version:refname | head -1`
- Branch: !`git branch --show-current`

## Release Preparation Process

1. <span class="hljs-strong">**Code Quality Validation**</span>
   - Run full test suite and ensure 100% pass rate
   - Execute linting and type checking
   - Verify no console.log or debugging statements
   - Check for TODO/FIXME comments that need addressing

2. <span class="hljs-strong">**Documentation Updates**</span>
   - Update CHANGELOG.md with new features and fixes
   - Verify README.md reflects current functionality
   - Update API documentation if applicable
   - Check that all examples in docs still work

3. <span class="hljs-strong">**Version Management**</span>
   - Analyze changes to determine version bump (major/minor/patch)
   - Update package.json version following semantic versioning
   - Create git tag with version number
   - Update any version references in code/docs

4. <span class="hljs-strong">**Build &amp; Package Verification**</span>
   - Create production build and verify no errors
   - Test build artifacts in clean environment
   - Verify bundle size hasn't increased unexpectedly
   - Run integration tests against production build

5. <span class="hljs-strong">**Security &amp; Dependencies**</span>
   - Run security audit on dependencies
   - Check for outdated packages that need updates
   - Verify no sensitive information in build artifacts
   - Validate environment variable usage

6. <span class="hljs-strong">**Release Notes Generation**</span>
   - Generate comprehensive release notes
   - Include breaking changes with migration guides
   - List new features with usage examples
   - Document bug fixes and performance improvements

---

# Example 6: Project Bootstrapping Command
# File: .claude/commands/init/new-feature.md

Bootstrap a new feature branch with complete setup:

## Feature Setup Process

1. <span class="hljs-strong">**Branch Management**</span>
   - Create feature branch from main/develop
   - Set up branch protection if needed
   - Configure tracking for remote branch

2. <span class="hljs-strong">**Directory Structure**</span>
   - Create feature-specific directories
   - Set up component/service/test folder structure
   - Add index files for clean imports

3. <span class="hljs-strong">**Boilerplate Generation**</span>
   - Generate initial component files
   - Create test files with basic structure
   - Set up API service files if needed
   - Add TypeScript interfaces/types

4. <span class="hljs-strong">**Configuration**</span>
   - Update routing configuration
   - Add feature flag if applicable
   - Update module exports
   - Configure any feature-specific settings

5. <span class="hljs-strong">**Documentation**</span>
   - Create feature documentation file
   - Add to project roadmap/feature list
   - Update architectural decision records
   - Create initial user stories/requirements

Usage: /new-feature user-dashboard
Creates complete feature branch setup

---

# Example 7: Debug Analysis Command
# File: .claude/commands/debug/analyze-error.md

Comprehensive error analysis and resolution:

## Error Investigation Process

1. <span class="hljs-strong">**Error Context Gathering**</span>
   - Capture full error stack trace and context
   - Identify error occurrence patterns (when/where/how often)
   - Check recent changes that might have introduced the issue
   - Gather user reports and reproduction steps

2. <span class="hljs-strong">**Code Analysis**</span>
   - Trace error source through call stack
   - Identify all code paths leading to the error
   - Check for similar patterns elsewhere in codebase
   - Analyze error handling in affected areas

3. <span class="hljs-strong">**Environment Investigation**</span>
   - Check if error is environment-specific
   - Verify configuration settings
   - Analyze logs for additional context
   - Check external service dependencies

4. <span class="hljs-strong">**Solution Development**</span>
   - Propose multiple solution approaches
   - Evaluate pros/cons of each approach
   - Implement most appropriate solution
   - Add preventive measures for similar issues

5. <span class="hljs-strong">**Validation &amp; Testing**</span>
   - Create test cases that reproduce the original error
   - Verify fix resolves the issue
   - Test edge cases and error boundaries
   - Ensure no regressions introduced

6. <span class="hljs-strong">**Documentation &amp; Prevention**</span>
   - Document the issue and solution
   - Update error handling patterns
   - Add monitoring/alerting if needed
   - Share learnings with team

---

# Example 8: Performance Optimization Command
# File: .claude/commands/performance/optimize.md

---
allowed-tools: Bash(npm:*</span>), Bash(yarn:<span class="hljs-emphasis">*), Node(*</span>)
<span class="hljs-section">description: Comprehensive performance analysis and optimization
---</span>

<span class="hljs-section">## Current Performance Baseline</span>
<span class="hljs-bullet">-</span> Bundle size: !<span class="hljs-code">`du -sh dist/ 2&gt;/dev/null || echo "No build found"`</span>
<span class="hljs-bullet">-</span> Dependencies: !<span class="hljs-code">`npm list --depth=0 | wc -l`</span>
<span class="hljs-bullet">-</span> Test performance: !<span class="hljs-code">`npm test -- --verbose 2&gt;/dev/null | grep "Time:" || echo "Run tests first"`</span>

<span class="hljs-section">## Performance Optimization Process</span>

<span class="hljs-bullet">1.</span> <span class="hljs-strong">**Bundle Analysis**</span>
<span class="hljs-bullet">   -</span> Analyze bundle size and composition
<span class="hljs-bullet">   -</span> Identify largest dependencies and modules
<span class="hljs-bullet">   -</span> Find duplicate dependencies
<span class="hljs-bullet">   -</span> Check for unused code and dead imports

<span class="hljs-bullet">2.</span> <span class="hljs-strong">**Code Optimization**</span>
<span class="hljs-bullet">   -</span> Review component render performance
<span class="hljs-bullet">   -</span> Optimize expensive operations and calculations
<span class="hljs-bullet">   -</span> Implement proper memoization strategies
<span class="hljs-bullet">   -</span> Check for memory leaks and cleanup issues

<span class="hljs-bullet">3.</span> <span class="hljs-strong">**Asset Optimization**</span>
<span class="hljs-bullet">   -</span> Optimize images and static assets
<span class="hljs-bullet">   -</span> Implement proper caching strategies
<span class="hljs-bullet">   -</span> Minimize and compress resources
<span class="hljs-bullet">   -</span> Use appropriate image formats and sizes

<span class="hljs-bullet">4.</span> <span class="hljs-strong">**Network Performance**</span>
<span class="hljs-bullet">   -</span> Optimize API calls and data fetching
<span class="hljs-bullet">   -</span> Implement proper loading states
<span class="hljs-bullet">   -</span> Add request caching where appropriate
<span class="hljs-bullet">   -</span> Minimize network round trips

<span class="hljs-bullet">5.</span> <span class="hljs-strong">**Runtime Performance**</span>
<span class="hljs-bullet">   -</span> Profile component re-renders
<span class="hljs-bullet">   -</span> Optimize state management
<span class="hljs-bullet">   -</span> Implement virtual scrolling for large lists
<span class="hljs-bullet">   -</span> Optimize animations and transitions

<span class="hljs-bullet">6.</span> <span class="hljs-strong">**Performance Monitoring**</span>
<span class="hljs-bullet">   -</span> Set up performance metrics tracking
<span class="hljs-bullet">   -</span> Add performance budgets to CI
<span class="hljs-bullet">   -</span> Create performance regression tests
<span class="hljs-bullet">   -</span> Document performance guidelines

---

<span class="hljs-section"># Example 9: Security Audit Command</span>
<span class="hljs-section"># File: .claude/commands/security/audit.md</span>

---
allowed-tools: Bash(npm:<span class="hljs-emphasis">*), Bash(git:*</span>), Node(<span class="hljs-emphasis">*)
description: Comprehensive security audit and vulnerability assessment
---

## Security Baseline
- Dependency vulnerabilities: !`npm audit --audit-level=moderate 2&gt;/dev/null || echo "Run npm audit"`
- Git history check: !`git log --oneline -10`
- Environment check: !`env | grep -E "(KEY|TOKEN|SECRET|PASSWORD)" | wc -l`

## Security Audit Process

1. <span class="hljs-strong">**Dependency Security**</span>
   - Run npm/yarn security audit
   - Check for known vulnerabilities in dependencies
   - Review dependency update requirements
   - Analyze dependency trust and maintenance status

2. <span class="hljs-strong">**Code Security Analysis**</span>
   - Scan for hardcoded secrets and credentials
   - Review authentication and authorization logic
   - Check input validation and sanitization
   - Analyze SQL injection and XSS vulnerabilities

3. <span class="hljs-strong">**Configuration Security**</span>
   - Review environment variable usage
   - Check for exposed configuration files
   - Validate security headers and policies
   - Review CORS and CSP configurations

4. <span class="hljs-strong">**Infrastructure Security**</span>
   - Review deployment security configurations
   - Check for exposed endpoints and services
   - Validate SSL/TLS implementation
   - Review access controls and permissions

5. <span class="hljs-strong">**Data Protection**</span>
   - Review data handling and storage practices
   - Check for PII data exposure
   - Validate encryption implementation
   - Review data backup and recovery procedures

6. <span class="hljs-strong">**Security Documentation**</span>
   - Update security guidelines and procedures
   - Document security decisions and rationale
   - Create incident response procedures
   - Generate security compliance reports

Usage: /security:audit
Performs complete security assessment of codebase</span>
</code></pre>
<h2 id="heading-3-advanced-command-features"><strong>3. Advanced Command Features</strong></h2>
<h3 id="heading-using-arguments-with-arguments"><strong>Using Arguments with $ARGUMENTS</strong></h3>
<p>Commands can accept dynamic parameters:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create command with arguments</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">'Fix issue #$ARGUMENTS following our coding standards'</span> &gt; .claude/commands/fix-issue.md

<span class="hljs-comment"># Usage in Claude Code</span>
/fix-issue 123
</code></pre>
<h3 id="heading-namespace-organization"><strong>Namespace Organization</strong></h3>
<p>Organize commands into subdirectories for better structure:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Creates namespaced commands</span>
mkdir -p .claude/commands/frontend
mkdir -p .claude/commands/backend
mkdir -p .claude/commands/deploy

<span class="hljs-comment"># Usage becomes:</span>
<span class="hljs-comment"># /frontend:component</span>
<span class="hljs-comment"># /backend:api-endpoint  </span>
<span class="hljs-comment"># /deploy:staging</span>
</code></pre>
<h3 id="heading-pre-execution-bash-commands"><strong>Pre-execution Bash Commands</strong></h3>
<p>Commands can run bash commands before execution using <code>!</code> prefix:</p>
<pre><code class="lang-markdown">---
allowed-tools: Bash(git status:<span class="hljs-emphasis">*), Bash(git diff:*</span>)
<span class="hljs-section">description: Review with git context
---</span>

<span class="hljs-section">## Context</span>
<span class="hljs-bullet">-</span> Current status: !<span class="hljs-code">`git status`</span>
<span class="hljs-bullet">-</span> Recent changes: !<span class="hljs-code">`git diff HEAD~1`</span>

[Rest of command...]
</code></pre>
<h2 id="heading-4-command-management"><strong>4. Command Management</strong></h2>
<h3 id="heading-listing-available-commands"><strong>Listing Available Commands</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># See all available commands</span>
/<span class="hljs-built_in">help</span>

<span class="hljs-comment"># Commands show their scope:</span>
<span class="hljs-comment"># (project) - Project-specific commands</span>
<span class="hljs-comment"># (user) - Personal commands across all projects</span>
<span class="hljs-comment"># (project:namespace) - Namespaced project commands</span>
</code></pre>
<h3 id="heading-command-discovery"><strong>Command Discovery</strong></h3>
<p>Claude Code automatically discovers:</p>
<ul>
<li><p><code>.claude/commands/*.md</code> files in your project</p>
</li>
<li><p><code>~/.claude/commands/*.md</code> files in your home directory</p>
</li>
<li><p>Commands from connected MCP servers</p>
</li>
</ul>
<h2 id="heading-5-best-practices"><strong>5. Best Practices</strong></h2>
<h3 id="heading-command-structure"><strong>Command Structure</strong></h3>
<ul>
<li><p>Use clear, descriptive filenames</p>
</li>
<li><p>Include comprehensive instructions</p>
</li>
<li><p>Add usage examples</p>
</li>
<li><p>Specify required tools/permissions</p>
</li>
</ul>
<h3 id="heading-organization-tips"><strong>Organization Tips</strong></h3>
<ul>
<li><p>Group related commands in namespaces</p>
</li>
<li><p>Use consistent naming conventions</p>
</li>
<li><p>Document command purposes and workflows</p>
</li>
<li><p>Keep commands focused on single responsibilities</p>
</li>
</ul>
<h3 id="heading-team-collaboration"><strong>Team Collaboration</strong></h3>
<ul>
<li><p>Commit <code>.claude/commands/</code> to version control</p>
</li>
<li><p>Document team-specific commands</p>
</li>
<li><p>Create project-specific workflows</p>
</li>
<li><p>Share successful command patterns</p>
</li>
</ul>
<h2 id="heading-6-real-world-examples"><strong>6. Real-World Examples</strong></h2>
<h3 id="heading-linear-integration-command"><strong>Linear Integration Command</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># .claude/commands/linear/create-tasks.md</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">'Convert this feature description into Linear tasks: $ARGUMENTS

1. Break down into granular tasks
2. Assign priorities and estimates
3. Create tasks in Linear via API
4. Set up proper labels and assignments
5. Link related tasks and dependencies'</span> &gt; .claude/commands/linear/create-tasks.md

<span class="hljs-comment"># Usage: /linear:create-tasks "Build user authentication system"</span>
</code></pre>
<h3 id="heading-ai-powered-code-generation"><strong>AI-Powered Code Generation</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># .claude/commands/ai/generate-component.md</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">'Generate a complete React component: $ARGUMENTS

1. Create TypeScript component with props interface
2. Add comprehensive JSDoc documentation  
3. Include accessibility attributes
4. Generate test file with coverage
5. Create Storybook stories
6. Add to component index exports'</span> &gt; .claude/commands/ai/generate-component.md

<span class="hljs-comment"># Usage: /ai:generate-component "UserProfile with avatar and edit functionality"</span>
</code></pre>
<p>Custom commands in Claude Code are incredibly powerful for automating repetitive workflows, ensuring consistency across your team, and creating reusable patterns that evolve with your project. They're essentially prompt templates that become part of your development toolkit!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755319987448/72541153-08a3-443d-8c36-52e51f8229ee.png" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[Supercharging Your Workflow with Claude Code: Commands, Hooks, Subagents & Beyond]]></title><description><![CDATA[🎙️ Podcast Insights
This article is inspired by Arvid Kahl and James Phoenix’s conversation on Claude Code.👉 Listen to the full episode here: Spotify Link.
They discussed how Claude can move beyond being “just a prompt assistant” into acting as a j...]]></description><link>https://rcmisk.com/supercharging-your-workflow-with-claude-code-commands-hooks-subagents-and-beyond</link><guid isPermaLink="true">https://rcmisk.com/supercharging-your-workflow-with-claude-code-commands-hooks-subagents-and-beyond</guid><dc:creator><![CDATA[rcmisk]]></dc:creator><pubDate>Sat, 16 Aug 2025 00:40:56 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-podcast-insights">🎙️ Podcast Insights</h2>
<p>This article is inspired by <a target="_blank" href="https://x.com/arvidkahl">Arvid Kah</a><a target="_blank" href="https://x.com/arvidkahl">l</a> and <a target="_blank" href="https://x.com/jamesaphoenix12">James Phoenix</a>’s conversation on Claude Code.<br />👉 Listen to the full episode here: <a target="_blank" href="https://open.spotify.com/episode/4truacRGLCR6iNslBJQYsM?si=6824d6179bed47e4">Spotify Lin</a><a target="_blank" href="https://open.spotify.com/episode/4truacRGLCR6iNslBJQYsM?si=6824d6179bed47e4">k.</a></p>
<p>They discussed how Claude can move beyond being “just a prompt assistant” into acting as a <strong>junior engineer that plans, executes, and self-checks work</strong>—a theme you’ll see woven throughout the sections below.</p>
<p>In a recent podcast episode, the hosts dove deep into how Claude Code is changing the way developers orchestrate their projects. It wasn’t just about prompts—it was about building <strong>self-regulating workflows</strong> that layer Claude into your repo, your CI, and even your nightly jobs.</p>
<p>Here’s a breakdown that combines those podcast insights with examples from Claude’s official docs.</p>
<hr />
<h2 id="heading-1-claude-commands-one-offs-and-scheduled-work">1. Claude Commands: One-Offs and Scheduled Work</h2>
<p>Commands don’t always have to be ad-hoc. You can treat Claude like a cron job. Run it once a week, every night, or even on every push.</p>
<ul>
<li><p><strong>Weekly one-off:</strong> Ask Claude to refresh your <code>docs.md</code> with the latest code updates.</p>
</li>
<li><p><strong>Hourly role enforcement:</strong> Have Claude check that your AI agents are still “assuming” their roles, overriding system prompts if needed.</p>
</li>
<li><p><strong>Nightly routines:</strong> Pick up a Linear ticket at 2 AM, plan implementation, run tests, verify results, and tee things up for the morning.</p>
</li>
</ul>
<p><strong>Example GitHub Action:</strong></p>
<pre><code class="lang-plaintext">on:
  schedule:
    - cron: "0 0 * * *"  # every night
jobs:
  nightly-docs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Update Docs
        uses: ./.github/actions/claude-pr-action
        with:
          trigger_phrase: "update docs"
          timeout_minutes: "30"
</code></pre>
<p>(<a target="_blank" href="https://docs.anthropic.com/en/docs/claude-code/github-actions">Docs reference</a>)</p>
<hr />
<h2 id="heading-2-claude-code-hooks-building-self-correcting-loops">2. Claude Code Hooks: Building Self-Correcting Loops</h2>
<p>One highlight from the podcast: don’t just tell Claude to do something—make it <strong>self-check</strong> along the way.</p>
<ul>
<li><p>Run type checks.</p>
</li>
<li><p>List errors.</p>
</li>
<li><p>Encourage self-evaluation between steps.</p>
</li>
<li><p>Force it into loops where it must fix errors before moving forward.</p>
</li>
</ul>
<p><strong>Hook Example (auto-format TypeScript after edits):</strong></p>
<pre><code class="lang-plaintext">"PostToolUse": [{
  "matcher": "Edit|Write",
  "hooks": [{
    "type": "command",
    "command": "jq -r '.tool_input.file_path' | { read f; if echo \"$f\" | grep -q '\\.ts$'; then npx prettier --write \"$f\"; fi; }"
  }]
}]
</code></pre>
<p>This ensures Claude doesn’t just “think” the code looks right—it <em>is</em> right.<br />(<a target="_blank" href="https://docs.anthropic.com/en/docs/claude-code/hooks-guide">Docs reference</a>)</p>
<hr />
<h2 id="heading-3-rules-with-claudemd">3. Rules with <code>Claude.md</code></h2>
<p>Another key insight: <strong>codify rules</strong> so Claude has a playbook.</p>
<ul>
<li><p>Put broad rules in the root <code>Claude.md</code>.</p>
</li>
<li><p>Keep localized rules in the folder they belong to (like <code>frontend/Claude.md</code>).</p>
</li>
</ul>
<hr />
<h2 id="heading-4-smarter-testing-strategies">4. Smarter Testing Strategies</h2>
<p>Instead of running all tests every time:</p>
<ul>
<li><p>Only run tests relevant to the changed files.</p>
</li>
<li><p>Use layers:</p>
<ul>
<li><p><strong>Unit tests</strong> for small functions.</p>
</li>
<li><p><strong>Integration tests</strong> for multiple modules.</p>
</li>
<li><p><strong>Playwright tests</strong> for the front end (slower, but realistic).</p>
</li>
</ul>
</li>
</ul>
<p>Claude can orchestrate this testing hierarchy, picking the right scope based on what’s changed.</p>
<hr />
<h2 id="heading-5-git-work-trees-amp-parallelization">5. Git Work Trees &amp; Parallelization</h2>
<p>The podcast covered using Git work trees and Supabase factory functions to <strong>parallelize testing</strong>.</p>
<ul>
<li><p>Run tests in parallel across work trees.</p>
</li>
<li><p>Let Claude coordinate those runs automatically.</p>
</li>
</ul>
<p>This is where automation starts looking like an in-house CI/CD engineer.</p>
<hr />
<h2 id="heading-6-supervisor-amp-subagents">6. Supervisor &amp; Subagents</h2>
<p>A powerful pattern: treat Claude as a <strong>supervisor layer</strong> with <strong>subagents</strong> underneath.</p>
<ul>
<li><p><strong>Supervisor:</strong> Orchestrates. Asks “have you really finished?” before marking a task done.</p>
</li>
<li><p><strong>Subagents:</strong> Each has a role (e.g., Python dev, frontend engineer, tester). Keep their <code>Claude.md</code> configs under 150 lines for clarity.</p>
</li>
</ul>
<p><strong>Example Subagent (Python Developer):</strong></p>
<pre><code class="lang-plaintext">---
name: python-dev
description: Write and debug Python code
tools: Read, Write, Bash, PipInstall
---
Act as a Python developer. Write clean, tested, maintainable code.
</code></pre>
<p>(<a target="_blank" href="https://docs.anthropic.com/en/docs/claude-code/sub-agents">Docs reference</a>)</p>
<p>Then in root <code>Claude.md</code>, define the order:</p>
<pre><code class="lang-plaintext">subagents:
  - python-dev
  - tester
  - code-reviewer
</code></pre>
<hr />
<h2 id="heading-7-vibe-cli-amp-knowledge-management">7. Vibe, CLI &amp; Knowledge Management</h2>
<p>Beyond code, Claude can manage the “vibe” of your project:</p>
<ul>
<li><p>Markdown → Claude constraints (enforce style guides, tone).</p>
</li>
<li><p>CLI commands for instant prompts (<code>/optimize</code>, <code>/fix-tests</code>).</p>
</li>
<li><p>Nightly ticket pickups: Claude can auto-assign itself a ticket, work overnight, and hand off results.</p>
</li>
</ul>
<p>(<a target="_blank" href="https://docs.anthropic.com/en/docs/claude-code/slash-commands#custom-slash-commands">Slash Commands reference</a>)</p>
<hr />
<h2 id="heading-8-putting-it-all-together">8. Putting It All Together</h2>
<p>Here’s what a Claude-powered dev loop can look like:</p>
<ol>
<li><p><strong>Nightly Claude Command</strong> runs: plan, code, test, verify.</p>
</li>
<li><p><strong>Hooks</strong> enforce rules and auto-fixes.</p>
</li>
<li><p><strong>Subagents</strong> take specialized roles.</p>
</li>
<li><p><strong>Supervisor</strong> layer orchestrates.</p>
</li>
<li><p><strong>Tests</strong> only run where needed, parallelized.</p>
</li>
<li><p><strong>Rules in Claude.md</strong> keep behavior consistent.</p>
</li>
<li><p><strong>CLI + Slash Commands</strong> make on-demand interactions fast.</p>
</li>
</ol>
<hr />
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>The big takeaway from the podcast: Claude isn’t just a co-pilot. With commands, hooks, rules, and subagents, it can become a <strong>self-managing junior engineer</strong> that runs on a schedule, double-checks its own work, and collaborates with your team.</p>
<p>Think of it as moving from “AI that answers” → “AI that executes.”</p>
]]></content:encoded></item><item><title><![CDATA[Version 2 - Claude Code Starter Prompt: For a Backend-First Content Generation API]]></title><description><![CDATA[🧠 Claude Code — Master Prompt (Backend-First Multi-Tenant GenAI SaaS API) - Version 2 of https://rcmisk.com/master-claude-code-starter-prompt-for-a-backend-first-content-generation-api
Role (System)
You are a senior backend architect + staff enginee...]]></description><link>https://rcmisk.com/version-2-claude-code-starter-prompt-for-a-backend-first-content-generation-api</link><guid isPermaLink="true">https://rcmisk.com/version-2-claude-code-starter-prompt-for-a-backend-first-content-generation-api</guid><category><![CDATA[claude.ai]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[SaaS]]></category><category><![CDATA[Python]]></category><category><![CDATA[Django]]></category><dc:creator><![CDATA[rcmisk]]></dc:creator><pubDate>Fri, 15 Aug 2025 17:57:12 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-claude-code-master-prompt-backend-first-multi-tenant-genai-saas-api-version-2-of-httpsrcmiskcommaster-claude-code-starter-prompt-for-a-backend-first-content-generation-apihttpsrcmiskcommaster-claude-code-starter-prompt-for-a-backend-first-content-generation-api">🧠 Claude Code — Master Prompt (Backend-First Multi-Tenant GenAI SaaS API) - Version 2 of <a target="_blank" href="https://rcmisk.com/master-claude-code-starter-prompt-for-a-backend-first-content-generation-api">https://rcmisk.com/master-claude-code-starter-prompt-for-a-backend-first-content-generation-api</a></h1>
<h2 id="heading-role-system">Role (System)</h2>
<p>You are a <strong>senior backend architect + staff engineer</strong>. Ship <strong>production-grade Python</strong> with <strong>Django 5 + Django Ninja</strong>, clean architecture, high test coverage, and excellent docs. Do <strong>not</strong> reveal chain-of-thought. Provide:</p>
<ul>
<li><p>succinct reasoning summaries,</p>
</li>
<li><p>concrete, runnable code,</p>
</li>
<li><p>a running <strong>TODO log</strong>,</p>
</li>
<li><p>work broken into <strong>Milestones → Tasks</strong> with visible progress.</p>
</li>
</ul>
<p>Target <strong>Python 3.11</strong>. Prefer explicit, verifiable implementations over abstractions.</p>
<hr />
<h2 id="heading-prime-directive">Prime Directive</h2>
<ol>
<li><p>Break the project into <strong>Milestones</strong>, each with <strong>Tasks</strong>.</p>
</li>
<li><p>Track Tasks in a YAML <strong>TODO log</strong>, marking ✅ as completed.</p>
</li>
<li><p>Deliver <strong>backend only</strong> (no frontend).</p>
</li>
<li><p>Stop after backend and wait for: <strong>“Proceed to frontend.”</strong></p>
</li>
</ol>
<hr />
<h2 id="heading-tech-stack-amp-constraints">Tech Stack &amp; Constraints</h2>
<ul>
<li><p><strong>Backend</strong>: Django 5, Django Ninja</p>
</li>
<li><p><strong>DB/Cache</strong>: PostgreSQL, Redis</p>
</li>
<li><p><strong>Auth</strong>: Google OAuth → JWT access/refresh in <strong>HTTP-only</strong> cookies (Secure, SameSite); CSRF on unsafe methods; strict CORS</p>
</li>
<li><p><strong>Providers</strong>: OpenAI, Anthropic (Claude), Google (Gemini) via <strong>pluggable adapters</strong></p>
</li>
<li><p><strong>Libs</strong>: PyJWT/SimpleJWT, httpx, pydantic/jsonschema, stripe, resend, posthog</p>
</li>
<li><p><strong>Testing</strong>: pytest + factory_boy + coverage</p>
</li>
<li><p><strong>Dev</strong>: Docker + docker-compose (web, db, redis, worker)</p>
</li>
<li><p><strong>Prod</strong>: Google Cloud Run</p>
</li>
<li><p><strong>Docs</strong>: Stripe-style (dark/light), code samples in <strong>curl, httpie, Python, JS, Java</strong></p>
</li>
</ul>
<hr />
<h2 id="heading-features">Features</h2>
<ul>
<li><p><strong>Multi-Tenant</strong>: org + membership + roles; <strong>org-scoped data everywhere</strong></p>
</li>
<li><p><strong>Billing</strong>: Stripe Checkout/Portal/Webhooks → plan → <strong>daily request limits</strong></p>
</li>
<li><p><strong>Usage</strong>: per-user/day counters; <strong>X/Reddit return exactly 10 items</strong> per call</p>
</li>
<li><p><strong>Social Generators</strong>:</p>
<ul>
<li><p><code>/social/x/generate</code> → 10 tweets</p>
</li>
<li><p><code>/social/reddit/generate</code> → 10 posts</p>
</li>
</ul>
</li>
<li><p><strong>Future</strong>: newsletter, landing page (returns JSON artifact)</p>
</li>
<li><p><strong>GenAI layer</strong>: provider-agnostic; <strong>strict JSON Schema validation</strong></p>
</li>
<li><p><strong>Emails</strong>: Resend; <strong>Analytics</strong>: PostHog</p>
</li>
</ul>
<h3 id="heading-quotas-per-userday-utc">Quotas (per user/day, UTC)</h3>
<ul>
<li><p>FREE = <strong>10</strong> requests/day (~100 items)</p>
</li>
<li><p>INDY ($20/mo) = <strong>10</strong> requests/day (~100 items)</p>
</li>
<li><p>PRO ($50/mo) = <strong>100</strong> requests/day (~1000 items)</p>
</li>
<li><p><code>ITEMS_PER_REQUEST = 10</code> for X/Reddit; landing page returns 1 artifact (still 1 request)</p>
</li>
</ul>
<hr />
<h2 id="heading-exact-twitter-master-prompt-verbatim-name-tweetbasedonhandle">Exact Twitter Master Prompt (verbatim) — name: <code>tweet_based_on_handle</code></h2>
<p><strong>Prompt: Mimic X Handle Tweet Style</strong></p>
<p>Analyze the X (Twitter) handle [INSERT HANDLE] and identify the tone, voice, and stylistic patterns of their posts.</p>
<p>Then, create a <strong>series of [X] original tweets</strong> written in the same style.</p>
<p><strong>Requirements:</strong></p>
<ul>
<li><p>Tweets should <strong>sound like</strong> [INSERT HANDLE] (tone, vocabulary, sentence rhythm).</p>
</li>
<li><p>Include the same balance of <strong>personal storytelling, reflections, and audience engagement</strong> that [INSERT HANDLE] typically uses.</p>
</li>
<li><p>Keep tweets <strong>under 280 characters</strong>.</p>
</li>
<li><p>Avoid direct copying; instead, generate <strong>new, original tweets</strong> that fit their voice.</p>
</li>
<li><p>Format each output as a standalone tweet, ready to post.</p>
</li>
</ul>
<p><strong>Example:</strong> If the handle is @robj3d3, the tweets should feel diary-like, candid, and self-reflective, often about building in public, life changes, and uncertainty.</p>
<p><strong>Server rule:</strong> Enforce 10 tweets per call in production.</p>
<hr />
<h2 id="heading-landing-page-generator-output-schema-verbatim">Landing Page Generator — Output Schema (verbatim)</h2>
<pre><code class="lang-plaintext">{
  "landing_page": {
    "name": "Generated Landing Page Name",
    "meta_title": "SEO optimized title (max 60 chars)",
    "meta_description": "SEO optimized description (max 160 chars)",
    "sections": [
      {
        "section_type": "hero_section",
        "title": "Hero Section",
        "order": 0,
        "hero_data": {
          "headline": "Compelling main headline",
          "subtitle": "Supporting subtitle that explains the value proposition",
          "cta_button": "Primary Call to Action"
        }
      },
      {
        "section_type": "feature_section",
        "title": "Features Section",
        "order": 1,
        "features": [
          { "title": "Feature 1 Title", "description": "Detailed feature description and benefits", "icon": "🚀" },
          { "title": "Feature 2 Title", "description": "Another compelling feature description", "icon": "⚡" },
          { "title": "Feature 3 Title", "description": "Third feature that completes the value proposition", "icon": "🔒" }
        ]
      },
      {
        "section_type": "testimonials",
        "title": "Testimonials Section",
        "order": 2,
        "testimonials_data": {
          "section_title": "What Our Customers Say",
          "description": "Brief section description",
          "testimonials": [
            { "quote": "Authentic customer testimonial quote", "name": "Customer Name", "title": "Job Title", "company": "Company Name" },
            { "quote": "Second testimonial quote", "name": "Another Customer", "title": "Their Position", "company": "Their Company" }
          ]
        }
      },
      {
        "section_type": "pricing",
        "title": "Pricing Section",
        "order": 3,
        "pricing_data": {
          "section_title": "Choose Your Plan",
          "description": "Pricing section description",
          "plans": [
            { "name": "Free Plan", "subtitle": "Perfect for getting started", "price": "Free", "icon": "🆓", "descriptions": ["Basic Feature 1", "Basic Feature 2", "Community Support"] },
            { "name": "Pro Plan", "subtitle": "For growing businesses", "price": "$29", "icon": "⭐", "descriptions": ["All Free features", "Advanced Feature 1", "Priority Support", "Analytics Dashboard"] },
            { "name": "Enterprise", "subtitle": "For large organizations", "price": "$99", "icon": "🏢", "descriptions": ["All Pro features", "Custom Integrations", "Dedicated Support", "SLA Guarantee"] }
          ]
        }
      },
      {
        "section_type": "faq",
        "title": "FAQ Section",
        "order": 4,
        "faq_data": {
          "section_title": "Frequently Asked Questions",
          "description": "Find answers to common questions about our service",
          "faqs": [
            { "question": "How does it work?", "answer": "Detailed explanation of how the product or service works" },
            { "question": "What are the pricing options?", "answer": "Explanation of pricing plans and what's included" },
            { "question": "Is there a free trial?", "answer": "Information about trial options and getting started" },
            { "question": "How do I get support?", "answer": "Details about customer support and help resources" }
          ]
        }
      },
      {
        "section_type": "contact",
        "title": "Contact Form Section",
        "order": 5,
        "contact_form_data": {
          "form_title": "Get in Touch",
          "description": "Have questions? We'd love to hear from you. Send us a message and we'll respond as soon as possible.",
          "name_label": "Full Name",
          "email_label": "Email Address",
          "message_label": "Your Message",
          "submit_button_text": "Send Message"
        }
      },
      {
        "section_type": "newsletter",
        "title": "Newsletter Section",
        "order": 6,
        "newsletter_data": {
          "headline": "Stay Updated with Latest Features",
          "description": "Join our newsletter for product updates and exclusive tips",
          "placeholder_text": "Enter your email",
          "button_text": "Subscribe Now",
          "privacy_text": "We respect your privacy and never share your information"
        }
      }
    ]
  }
}
</code></pre>
<p><strong>Content rules:</strong> real emoji (🚀 ⚡ 🔒 🤖 ⭐ 🏢), prices short (<code>Free</code>, <code>$29</code>, <code>$99</code>), headlines &lt; 60 chars, meta title &lt; 60, meta desc &lt; 160, conversion-focused tone.</p>
<hr />
<h2 id="heading-repo-layout-backend-only">Repo Layout (backend only)</h2>
<pre><code class="lang-plaintext">backend/
├─ manage.py
├─ pyproject.toml / requirements.txt
├─ docker-compose.yml
├─ Dockerfile
├─ .env.example
├─ claude.md                  # ROOT: global rules &amp; rituals (REQUIRED)
├─ backend/                   # settings, urls, asgi
│  ├─ settings.py
│  ├─ urls.py
│  └─ asgi.py
├─ apps/
│  ├─ authx/                  # google oauth, jwt cookies, csrf
│  │  └─ claude.md            # MODULE rules (REQUIRED)
│  ├─ orgs/
│  │  └─ claude.md
│  ├─ billing/
│  │  └─ claude.md
│  ├─ usage/
│  │  └─ claude.md
│  ├─ notifications/
│  │  └─ claude.md
│  ├─ analytics/
│  │  └─ claude.md
│  └─ social/
│     └─ claude.md
├─ social_media/
│  ├─ base/
│  │  └─ claude.md
│  ├─ x/
│  │  └─ claude.md
│  └─ reddit/
│     └─ claude.md
├─ generators/
│  ├─ base/
│  │  └─ claude.md
│  ├─ social_x/
│  │  └─ claude.md
│  ├─ social_reddit/
│  │  └─ claude.md
│  └─ landing_page/
│     └─ claude.md
├─ providers/
│  ├─ openai/
│  │  └─ claude.md
│  ├─ anthropic/
│  │  └─ claude.md
│  └─ google/
│     └─ claude.md
├─ tests/
│  └─ claude.md
└─ README.md
</code></pre>
<hr />
<h2 id="heading-new-claudemdhttpclaudemd-authoring-rules-required"><strong>NEW:</strong> <a target="_blank" href="http://claude.md"><code>claude.md</code></a> authoring rules (REQUIRED)</h2>
<p>Claude must <strong>create and write</strong> these files:</p>
<h3 id="heading-1-root-claudemdhttpclaudemd-project-wide">1) Root <a target="_blank" href="http://claude.md"><code>claude.md</code></a> (project-wide)</h3>
<p><strong>Sections (use these exact headings):</strong></p>
<ul>
<li><p>Purpose &amp; Scope</p>
</li>
<li><p>Golden Rules (non-negotiables)</p>
</li>
<li><p>Milestone Ritual (how to mark ✅ in TODO log)</p>
</li>
<li><p>Code Style (imports, typing, lint level, logging)</p>
</li>
<li><p>Test Policy (coverage target, factories, mocks)</p>
</li>
<li><p>Security Policy (cookies, CSRF, CORS, secrets)</p>
</li>
<li><p>Deployment Policy (Docker, Cloud Run, envs)</p>
</li>
<li><p>Done Definition (per PR / per milestone)</p>
</li>
</ul>
<h3 id="heading-2-per-module-claudemdhttpclaudemd-each-module-folder">2) Per-module <a target="_blank" href="http://claude.md"><code>claude.md</code></a> (each module folder)</h3>
<p><strong>Sections:</strong></p>
<ul>
<li><p>Module Purpose</p>
</li>
<li><p>Public Interfaces (endpoints/services)</p>
</li>
<li><p>Data Contracts (pydantic/jsonschema)</p>
</li>
<li><p>Invariants &amp; Guards (preconditions/postconditions)</p>
</li>
<li><p>Test Checklist (unit/integration)</p>
</li>
<li><p>Security Notes (authz, PII, tenancy)</p>
</li>
<li><p>Observability (events/metrics)</p>
</li>
<li><p>Pitfalls &amp; Anti-patterns</p>
</li>
<li><p>Local Dev Tips (seed data, curl samples)</p>
</li>
</ul>
<blockquote>
<p><strong>Style</strong>: keep each <a target="_blank" href="http://claude.md"><code>claude.md</code></a> concise (≈200–500 words), bulleted where possible, and actionable.</p>
</blockquote>
<hr />
<h2 id="heading-environment-envexample">Environment (.env.example)</h2>
<p>Include keys for: Django, DB, Redis, Google OAuth, JWT, Stripe, Resend, PostHog, X API, Reddit API, Cloud Run <code>PORT</code>.<br />Use the exact names already specified earlier in our spec.</p>
<hr />
<h2 id="heading-api-stripe-style">API (Stripe-style)</h2>
<ul>
<li><p>Base URL: <code>/api/v1</code></p>
</li>
<li><p>Headers: <code>X-Org-Id</code>, <code>X-CSRF-Token</code></p>
</li>
<li><p>Cookies: <code>access_token</code>, <code>refresh_token</code> (HTTP-only)</p>
</li>
<li><p>Endpoints:</p>
<ul>
<li><p><code>GET /health</code></p>
</li>
<li><p><code>GET /usage/today</code></p>
</li>
<li><p><code>POST /social/x/generate</code></p>
</li>
<li><p><code>POST /social/reddit/generate</code></p>
</li>
<li><p><code>POST /content/landing-page/generate</code></p>
</li>
<li><p><code>POST /billing/checkout</code></p>
</li>
<li><p><code>POST /billing/portal</code></p>
</li>
<li><p><code>POST /billing/webhook</code> (verify signature)</p>
</li>
</ul>
</li>
</ul>
<p><strong>Error example</strong></p>
<pre><code class="lang-plaintext">{
  "error": {
    "code": "DAILY_REQUEST_LIMIT_REACHED",
    "message": "Daily request limit reached (10/10).",
    "details": { "date": "2025-08-14", "limit_requests": 10, "used_requests": 10 }
  }
}
</code></pre>
<hr />
<h2 id="heading-testing-coverage">Testing Coverage</h2>
<p>Auth (OAuth → JWT cookies; refresh; CSRF) • Tenancy (org scoping) • Billing (webhook signature; plan mapping) • Usage (atomic; UTC rollover; 429) • Generators (10 items; schema validation) • Providers (mock adapters) • Security (CORS, cookies, input validation).</p>
<hr />
<h2 id="heading-dev-amp-deploy">Dev &amp; Deploy</h2>
<p><strong>Local:</strong> <code>docker-compose up --build</code> (web, db, redis)<br /><strong>Prod:</strong> Cloud Run (build + deploy commands with envs)</p>
<hr />
<h2 id="heading-output-discipline-strict">Output Discipline (strict)</h2>
<ul>
<li><p><strong>Milestones + Tasks</strong> first, then code.</p>
</li>
<li><p>Produce <strong>real, runnable code</strong> + migrations (no placeholders).</p>
</li>
<li><p>Keep a YAML <strong>TODO log</strong> and mark ✅ as tasks complete.</p>
</li>
<li><p>Provide short reasoning summaries (no hidden chain-of-thought).</p>
</li>
<li><p>If blocked, list up to 5 precise questions, then proceed with sensible defaults.</p>
</li>
<li><p><strong>Create all</strong> <a target="_blank" href="http://claude.md"><code>claude.md</code></a> files exactly as specified.</p>
</li>
</ul>
<hr />
<h2 id="heading-deliverables-emit-in-this-order-as-fenced-blocks">Deliverables (emit in this order as fenced blocks)</h2>
<ol>
<li><p><a target="_blank" href="http://summary.md"><code>summary.md</code></a> — 6–10 bullets + Open Issues (≤5)</p>
</li>
<li><p><code>todo.yaml</code> — milestones → tasks with checkboxes</p>
</li>
<li><p><code>tree.txt</code> — final repo tree (includes all <a target="_blank" href="http://claude.md"><code>claude.md</code></a>)</p>
</li>
<li><p><code>.env.example</code></p>
</li>
<li><p><code>requirements.txt</code> or <code>pyproject.toml</code></p>
</li>
<li><p><code>backend/backend/</code><a target="_blank" href="http://settings.py"><code>settings.py</code></a></p>
</li>
<li><p><code>docker-compose.yml</code> and <code>Dockerfile</code></p>
</li>
<li><p><code>migrations_and_</code><a target="_blank" href="http://models.md"><code>models.md</code></a> — models + migration commands</p>
</li>
<li><p><a target="_blank" href="http://routes.md"><code>routes.md</code></a> — list endpoints + auth/CSRF headers</p>
</li>
<li><p><code>tests_</code><a target="_blank" href="http://summary.md"><code>summary.md</code></a> — what tests cover + how to run</p>
</li>
<li><p><strong>All</strong> <a target="_blank" href="http://claude.md"><code>claude.md</code></a> files (root + modules), each in its own fence</p>
</li>
<li><p>Key source files (urls/asgi/apps/routers/adapters/schemas/providers/etc.)</p>
</li>
</ol>
<hr />
<h2 id="heading-anti-drift-self-check-claude-must-run-before-emitting-code">Anti-Drift Self-Check (Claude must run before emitting code)</h2>
<ul>
<li><p>Do X/Reddit endpoints <strong>always</strong> return <strong>10 items</strong>?</p>
</li>
<li><p>Are usage limits enforced <strong>before</strong> generation?</p>
</li>
<li><p>Are all queries <strong>org-scoped</strong>?</p>
</li>
<li><p>Are JWT cookies <strong>HttpOnly + Secure + SameSite</strong> and CSRF enforced?</p>
</li>
<li><p>Are Stripe webhooks signature-verified and plan → requests/day mapped?</p>
</li>
<li><p>Do I include <strong>all</strong> <a target="_blank" href="http://claude.md"><code>claude.md</code></a> files with the required headings?</p>
</li>
<li><p>Does <code>docker-compose up --build</code> run web+db+redis?</p>
</li>
<li><p>Does <code>pytest -q</code> pass with meaningful coverage?</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[README.md generated from https://rcmisk.com/master-claude-code-starter-prompt-for-a-backend-first-content-generation-api]]></title><description><![CDATA[# DataPages

Convert CSV files into beautiful, searchable, sortable data pages with Google authentication and comprehensive analytics.

## Features

- 🔐 **Google OAuth Authentication** - Secure user management
- 📊 **CSV Processing** - Upload and ca...]]></description><link>https://rcmisk.com/readmemd-generated-from-saas-prompt</link><guid isPermaLink="true">https://rcmisk.com/readmemd-generated-from-saas-prompt</guid><category><![CDATA[SaaS]]></category><category><![CDATA[AI]]></category><category><![CDATA[claude.ai]]></category><dc:creator><![CDATA[rcmisk]]></dc:creator><pubDate>Fri, 15 Aug 2025 06:01:20 GMT</pubDate><content:encoded><![CDATA[<pre><code class="lang-markdown"><span class="hljs-section"># DataPages</span>

Convert CSV files into beautiful, searchable, sortable data pages with Google authentication and comprehensive analytics.

<span class="hljs-section">## Features</span>

<span class="hljs-bullet">-</span> 🔐 <span class="hljs-strong">**Google OAuth Authentication**</span> - Secure user management
<span class="hljs-bullet">-</span> 📊 <span class="hljs-strong">**CSV Processing**</span> - Upload and categorize CSV data automatically  
<span class="hljs-bullet">-</span> 🎨 <span class="hljs-strong">**Beautiful UI**</span> - Clean, responsive tables with search and sorting
<span class="hljs-bullet">-</span> 🔒 <span class="hljs-strong">**Privacy Controls**</span> - Private, Semi-Public, and Public access modes
<span class="hljs-bullet">-</span> 📈 <span class="hljs-strong">**Page View Analytics**</span> - Track views for authenticated and anonymous users
<span class="hljs-bullet">-</span> 🎯 <span class="hljs-strong">**Smart Notifications**</span> - Modern toast notifications and modal dialogs
<span class="hljs-bullet">-</span> 👑 <span class="hljs-strong">**Admin Dashboard**</span> - Manage users, view analytics, and system stats
<span class="hljs-bullet">-</span> 🚀 <span class="hljs-strong">**Production Ready**</span> - Optimized for Vercel deployment with Supabase

<span class="hljs-section">## Quick Start</span>

<span class="hljs-section">### Local Development</span>

<span class="hljs-bullet">1.</span> <span class="hljs-strong">**Clone and install:**</span>
   <span class="hljs-code">```bash
   git clone &lt;your-repo&gt;
   cd csv_to_landingpage
   npm install
   ```</span>

<span class="hljs-bullet">2.</span> <span class="hljs-strong">**Set up Google OAuth:**</span>
<span class="hljs-bullet">   -</span> Go to [<span class="hljs-string">Google Cloud Console</span>](<span class="hljs-link">https://console.cloud.google.com/</span>)
<span class="hljs-bullet">   -</span> Create a new project or select existing one
<span class="hljs-bullet">   -</span> Enable Google+ API
<span class="hljs-bullet">   -</span> Create OAuth 2.0 Client ID credentials
<span class="hljs-bullet">   -</span> Add authorized redirect URI: <span class="hljs-code">`http://localhost:3001/api/auth/callback/google`</span>
<span class="hljs-bullet">   -</span> Copy Client ID and Client Secret

<span class="hljs-bullet">3.</span> <span class="hljs-strong">**Set up environment variables:**</span>
   <span class="hljs-code">```bash
   cp .env.example .env.local
   ```</span>

   Edit <span class="hljs-code">`.env.local`</span> with your values:
   <span class="hljs-code">```bash
   NEXTAUTH_URL=http://localhost:3001
   NEXTAUTH_SECRET=your-secret-here  # Generate with: openssl rand -base64 32
   GOOGLE_CLIENT_ID=your-google-client-id
   GOOGLE_CLIENT_SECRET=your-google-client-secret
   DATABASE_URL="file:./dev.db"
   SUPER_ADMIN_EMAIL=your-email@gmail.com
   ```</span>

<span class="hljs-bullet">4.</span> <span class="hljs-strong">**Set up database (SQLite for local):**</span>
   <span class="hljs-code">```bash
   npx prisma migrate dev --name init
   # This creates the database and all required tables
   ```</span>

<span class="hljs-bullet">5.</span> <span class="hljs-strong">**Start development server:**</span>
   <span class="hljs-code">```bash
   npm run dev
   ```</span>
   Open [<span class="hljs-string">http://localhost:3001</span>](<span class="hljs-link">http://localhost:3001</span>)

<span class="hljs-bullet">6.</span> <span class="hljs-strong">**Test the application:**</span>
<span class="hljs-bullet">   -</span> Sign in with your Google account
<span class="hljs-bullet">   -</span> Create a data page by uploading a CSV
<span class="hljs-bullet">   -</span> View your dashboard at <span class="hljs-code">`http://localhost:3001/dashboard`</span>
<span class="hljs-bullet">   -</span> Access admin dashboard at <span class="hljs-code">`http://localhost:3001/admin`</span> (super admin only)

<span class="hljs-section">### Production Deployment (Vercel + Supabase)</span>

<span class="hljs-bullet">1.</span> <span class="hljs-strong">**Set up Supabase database (Recommended):**</span>

   <span class="hljs-strong">**Create Supabase Project:**</span>
<span class="hljs-bullet">   -</span> Go to [<span class="hljs-string">supabase.com</span>](<span class="hljs-link">https://supabase.com</span>) and create account
<span class="hljs-bullet">   -</span> Create new project (note: takes ~2 minutes to provision)
<span class="hljs-bullet">   -</span> Go to Settings → Database
<span class="hljs-bullet">   -</span> Copy the connection string (URI format)

   <span class="hljs-strong">**Alternative Options:**</span>
<span class="hljs-bullet">   -</span> <span class="hljs-strong">**Neon**</span>: [<span class="hljs-string">neon.tech</span>](<span class="hljs-link">https://neon.tech</span>) - Free PostgreSQL
<span class="hljs-bullet">   -</span> <span class="hljs-strong">**Vercel Postgres**</span>: In Vercel dashboard → Storage → Create Database
<span class="hljs-bullet">   -</span> <span class="hljs-strong">**Railway/PlanetScale**</span>: Other PostgreSQL providers

<span class="hljs-bullet">2.</span> <span class="hljs-strong">**Deploy to Vercel:**</span>
   <span class="hljs-code">```bash
   # Install Vercel CLI if needed
   npm i -g vercel

   # Deploy
   vercel --prod
   ```</span>

<span class="hljs-bullet">3.</span> <span class="hljs-strong">**Configure environment variables in Vercel:**</span>

   Go to Vercel Dashboard → Your Project → Settings → Environment Variables:

   <span class="hljs-code">```bash
   NEXTAUTH_URL=https://your-app-name.vercel.app
   NEXTAUTH_SECRET=your-production-secret-here
   GOOGLE_CLIENT_ID=your-google-client-id
   GOOGLE_CLIENT_SECRET=your-google-client-secret
   DATABASE_URL=postgresql://user:password@host/database?sslmode=require
   SUPER_ADMIN_EMAIL=your-email@gmail.com
   ```</span>

<span class="hljs-bullet">4.</span> <span class="hljs-strong">**Update Google OAuth for production:**</span>
<span class="hljs-bullet">   -</span> Go to Google Cloud Console → Your Project → Credentials
<span class="hljs-bullet">   -</span> Edit your OAuth 2.0 Client ID
<span class="hljs-bullet">   -</span> Add authorized redirect URI: <span class="hljs-code">`https://your-app-name.vercel.app/api/auth/callback/google`</span>
<span class="hljs-bullet">   -</span> Save changes

<span class="hljs-bullet">5.</span> <span class="hljs-strong">**Verify deployment:**</span>
<span class="hljs-bullet">   -</span> Visit your production URL
<span class="hljs-bullet">   -</span> Test Google sign-in
<span class="hljs-bullet">   -</span> Create a data page by uploading a CSV
<span class="hljs-bullet">   -</span> View dashboard at <span class="hljs-code">`/dashboard`</span>
<span class="hljs-bullet">   -</span> Access admin dashboard at <span class="hljs-code">`/admin`</span> (super admin only)

<span class="hljs-bullet">6.</span> <span class="hljs-strong">**Run database migrations in production:**</span>

   <span class="hljs-strong">**Option A: Supabase Dashboard (Recommended)**</span>
   <span class="hljs-code">```sql
   -- Run this SQL in Supabase SQL Editor:

   -- Create PageView table for analytics
   CREATE TABLE IF NOT EXISTS "PageView" (
       "id" TEXT NOT NULL,
       "csvUploadId" TEXT NOT NULL,
       "userId" TEXT,
       "ipAddress" TEXT NOT NULL,
       "userAgent" TEXT,
       "sessionId" TEXT,
       "viewedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
       CONSTRAINT "PageView_pkey" PRIMARY KEY ("id")
   );

   -- Create indexes
   CREATE INDEX IF NOT EXISTS "PageView_csvUploadId_idx" ON "PageView"("csvUploadId");
   CREATE INDEX IF NOT EXISTS "PageView_userId_idx" ON "PageView"("userId");
   CREATE INDEX IF NOT EXISTS "PageView_viewedAt_idx" ON "PageView"("viewedAt");

   -- Add foreign key constraints
   ALTER TABLE "PageView" ADD CONSTRAINT "PageView_csvUploadId_fkey" 
   FOREIGN KEY ("csvUploadId") REFERENCES "CsvUpload"("id") ON DELETE CASCADE ON UPDATE CASCADE;

   ALTER TABLE "PageView" ADD CONSTRAINT "PageView_userId_fkey" 
   FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
   ```</span>

   <span class="hljs-strong">**Option B: API Migration Endpoint**</span>
   <span class="hljs-code">```bash
   # After deployment, call the migration API as an admin:
   curl -X POST https://your-domain.com/api/migrate-pageview
   ```</span>

<span class="hljs-bullet">7.</span> <span class="hljs-strong">**Fix admin access if needed:**</span>
   <span class="hljs-code">```sql
   -- Run in Supabase SQL Editor to restore admin access:
   UPDATE "User" SET role = 'admin' WHERE email = 'your-email@domain.com';
   ```</span>

   Or use the fix endpoint: <span class="hljs-code">`https://your-domain.com/api/fix-admin`</span>

<span class="hljs-bullet">8.</span> <span class="hljs-strong">**Verify deployment:**</span>
<span class="hljs-bullet">   -</span> Visit your production URL and sign in
<span class="hljs-bullet">   -</span> Create a data page by uploading a CSV  
<span class="hljs-bullet">   -</span> Check dashboard at <span class="hljs-code">`/dashboard`</span> - should show view counts
<span class="hljs-bullet">   -</span> Access admin at <span class="hljs-code">`/admin`</span> - should show analytics
<span class="hljs-bullet">   -</span> Test page view tracking by visiting data pages

<span class="hljs-section">## Admin Management</span>

<span class="hljs-section">### Roles &amp; Permissions</span>

<span class="hljs-bullet">-</span> <span class="hljs-strong">**Super Admin**</span> (set via <span class="hljs-code">`SUPER_ADMIN_EMAIL`</span> env var):
<span class="hljs-bullet">  -</span> Full access to admin dashboard
<span class="hljs-bullet">  -</span> Can add/remove other admins
<span class="hljs-bullet">  -</span> Cannot be removed by other admins
<span class="hljs-bullet">  -</span> View all users and CSV uploads

<span class="hljs-bullet">-</span> <span class="hljs-strong">**Regular Admins**</span> (added via admin dashboard):
<span class="hljs-bullet">  -</span> View admin dashboard and user analytics
<span class="hljs-bullet">  -</span> View all users and CSV uploads
<span class="hljs-bullet">  -</span> Cannot add/remove other admins

<span class="hljs-section">### Managing Admins</span>

<span class="hljs-strong">**Local Development:**</span>
<span class="hljs-bullet">1.</span> Set your email in <span class="hljs-code">`.env.local`</span> as <span class="hljs-code">`SUPER_ADMIN_EMAIL`</span>
<span class="hljs-bullet">2.</span> Sign in with that email
<span class="hljs-bullet">3.</span> Visit <span class="hljs-code">`http://localhost:3001/admin`</span>
<span class="hljs-bullet">4.</span> Use "Manage Admins" section to add other admins

<span class="hljs-strong">**Production:**</span>
<span class="hljs-bullet">1.</span> Set your email in Vercel env vars as <span class="hljs-code">`SUPER_ADMIN_EMAIL`</span>
<span class="hljs-bullet">2.</span> Deploy and sign in with that email
<span class="hljs-bullet">3.</span> Visit <span class="hljs-code">`https://your-app.vercel.app/admin`</span>
<span class="hljs-bullet">4.</span> Add other admins by entering their email addresses

<span class="hljs-strong">**Adding Admins:**</span>
<span class="hljs-bullet">-</span> Users must sign in at least once before being made admin
<span class="hljs-bullet">-</span> Enter their exact Google email address
<span class="hljs-bullet">-</span> They'll immediately gain admin access

<span class="hljs-strong">**Removing Admins:**</span>
<span class="hljs-bullet">-</span> Only super admin can remove other admins
<span class="hljs-bullet">-</span> Super admin cannot be removed
<span class="hljs-bullet">-</span> Removed admins lose access immediately

<span class="hljs-section">## Tech Stack</span>

<span class="hljs-bullet">-</span> <span class="hljs-strong">**Frontend**</span>: Next.js 15, React 18, Tailwind CSS, shadcn/ui, Lucide React
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Backend**</span>: Next.js API routes, NextAuth.js (Google OAuth)
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Database**</span>: Supabase PostgreSQL (prod) / SQLite (dev)  
<span class="hljs-bullet">-</span> <span class="hljs-strong">**ORM**</span>: Prisma with direct client instantiation
<span class="hljs-bullet">-</span> <span class="hljs-strong">**UI Components**</span>: shadcn/ui (Toast, Dialog, Button, Table, Card)
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Analytics**</span>: Custom page view tracking with user/anonymous support
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Deployment**</span>: Vercel with custom domain aliasing
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Notifications**</span>: Toast notifications and modal confirmations

<span class="hljs-section">## Security Features</span>

<span class="hljs-bullet">-</span> <span class="hljs-strong">**Server-side admin checks**</span> - Admin verification happens on server only
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Database audit trails**</span> - All admin actions logged to console
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Secure environment variables**</span> - Sensitive data never exposed to client
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Google OAuth integration**</span> - Secure authentication with Google
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Role-based access control**</span> - Super admin vs regular admin permissions
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Protected API routes**</span> - All admin endpoints require authentication
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Automatic role sync**</span> - Super admin role automatically assigned

<span class="hljs-section">## Analytics &amp; Page View Tracking</span>

<span class="hljs-section">### How It Works</span>

<span class="hljs-bullet">-</span> <span class="hljs-strong">**Automatic Tracking**</span>: Every data page visit is automatically tracked
<span class="hljs-bullet">-</span> <span class="hljs-strong">**User Types**</span>: Supports both authenticated users and anonymous visitors  
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Deduplication**</span>: Prevents duplicate views within 30 minutes from same user/session
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Privacy Safe**</span>: Only tracks IP, user agent, and timestamp for anonymous users

<span class="hljs-section">### View Analytics</span>

<span class="hljs-strong">**User Dashboard**</span> (<span class="hljs-code">`/dashboard`</span>):
<span class="hljs-bullet">-</span> View counts for each of your data pages
<span class="hljs-bullet">-</span> Total views stat card
<span class="hljs-bullet">-</span> Eye icons showing view numbers

<span class="hljs-strong">**Admin Dashboard**</span> (<span class="hljs-code">`/admin`</span>):  
<span class="hljs-bullet">-</span> System-wide view statistics
<span class="hljs-bullet">-</span> View counts for all users' pages
<span class="hljs-bullet">-</span> Analytics breakdowns by access mode

<span class="hljs-strong">**Data Tracked:**</span>
<span class="hljs-bullet">-</span> Page views with timestamps
<span class="hljs-bullet">-</span> User ID (for authenticated users) or session ID (anonymous)
<span class="hljs-bullet">-</span> IP address (for security/deduplication)
<span class="hljs-bullet">-</span> User agent string (browser info)

<span class="hljs-section">## Testing in Development</span>

<span class="hljs-section">### Test Analytics Locally</span>

<span class="hljs-bullet">1.</span> <span class="hljs-strong">**Start development server:**</span>
   <span class="hljs-code">```bash
   npm run dev
   ```</span>

<span class="hljs-bullet">2.</span> <span class="hljs-strong">**Create test data pages:**</span>
<span class="hljs-bullet">   -</span> Sign in with Google
<span class="hljs-bullet">   -</span> Upload a few CSV files with different privacy settings
<span class="hljs-bullet">   -</span> Note the subdomain names created

<span class="hljs-bullet">3.</span> <span class="hljs-strong">**Test page view tracking:**</span>
   <span class="hljs-code">```bash
   # Visit data pages in browser
   open http://localhost:3001/subdomain/your-test-page

   # Check dashboard for view counts
   open http://localhost:3001/dashboard

   # Check admin analytics (if admin)
   open http://localhost:3001/admin
   ```</span>

<span class="hljs-bullet">4.</span> <span class="hljs-strong">**Test different user scenarios:**</span>
<span class="hljs-bullet">   -</span> <span class="hljs-strong">**Anonymous user**</span>: Visit data pages in incognito mode
<span class="hljs-bullet">   -</span> <span class="hljs-strong">**Authenticated user**</span>: Sign in and visit pages
<span class="hljs-bullet">   -</span> <span class="hljs-strong">**Different privacy levels**</span>: Test Private/Semi-Public/Public pages

<span class="hljs-bullet">5.</span> <span class="hljs-strong">**Verify in database:**</span>
   <span class="hljs-code">```bash
   # Check SQLite database
   npx prisma studio
   # Look at PageView table entries
   ```</span>

<span class="hljs-section">### Test Toast Notifications</span>

<span class="hljs-bullet">-</span> <span class="hljs-strong">**Upload success**</span>: Create new data page → Should show success toast
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Delete confirmation**</span>: Try deleting uploads → Should show modal dialog  
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Privacy changes**</span>: Change access modes → Should show toast feedback
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Error handling**</span>: Try invalid operations → Should show error toasts

<span class="hljs-section">## Troubleshooting</span>

<span class="hljs-section">### Critical Issues We Encountered</span>

<span class="hljs-strong">**❌ "Failed to read data" / Slow Loading:**</span>
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Cause**</span>: Broken import paths in API endpoints (<span class="hljs-code">`@/lib/auth`</span>, <span class="hljs-code">`@/lib/prisma`</span>)
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Fix**</span>: Use relative imports: <span class="hljs-code">`../../auth/[...nextauth]/route`</span> and direct <span class="hljs-code">`PrismaClient`</span>
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Files affected**</span>: Most API routes in <span class="hljs-code">`/api/`</span> folders

<span class="hljs-strong">**❌ "Can't fetch uploads" / Dashboard errors:**</span>
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Cause**</span>: Invalid <span class="hljs-code">`session.user.id`</span> references (NextAuth doesn't include ID by default)
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Fix**</span>: Use <span class="hljs-code">`session.user.email`</span> and lookup user ID from database first
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Files affected**</span>: <span class="hljs-code">`/api/user/uploads/*`</span>, <span class="hljs-code">`/api/analytics/views`</span>

<span class="hljs-strong">**❌ Admin APIs returning 500 errors:**</span>
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Cause**</span>: Missing functions like <span class="hljs-code">`requireAdmin()`</span>, <span class="hljs-code">`isAdmin()`</span>, <span class="hljs-code">`isSuperAdmin()`</span>
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Fix**</span>: Implement direct role checks with database queries
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Files affected**</span>: <span class="hljs-code">`/api/admin/*`</span> endpoints

<span class="hljs-strong">**❌ Page view tracking not working:**</span>
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Cause**</span>: Missing <span class="hljs-code">`PageView`</span> table in database
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Fix**</span>: Run the migration SQL in Supabase dashboard or use migration API

<span class="hljs-strong">**❌ Lost admin access after deployment:**</span>
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Cause**</span>: Admin role not set in database during fixes
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Fix**</span>: Use <span class="hljs-code">`/api/fix-admin`</span> endpoint or update role in Supabase directly

<span class="hljs-section">### Common Development Issues</span>

<span class="hljs-strong">**Database not found in development:**</span>
<span class="hljs-code">```bash
# Delete existing migrations and recreate
rm -rf prisma/migrations
npx prisma migrate dev --name init
```</span>

<span class="hljs-strong">**Import errors during build:**</span>
<span class="hljs-code">```bash
# Check for these common issues:
# 1. Broken @/lib/* imports - use relative paths
# 2. Missing PrismaClient instantiation  
# 3. Invalid session.user.id references
```</span>

<span class="hljs-strong">**Page view tracking not appearing:**</span>
<span class="hljs-code">```bash
# Check if PageView table exists:
# 1. In Supabase: Go to Table Editor, look for PageView table
# 2. Missing? Run the migration SQL from production setup section
# 3. Check browser network tab for /api/analytics/pageview calls
```</span>

<span class="hljs-section">### API Debugging</span>

<span class="hljs-strong">**Check API endpoint status:**</span>
<span class="hljs-code">```bash
# Test if APIs are working:
curl https://your-domain.com/api/user/uploads
curl https://your-domain.com/api/admin/uploads  
curl -X POST https://your-domain.com/api/analytics/pageview -d '{"subdomain":"test"}'
```</span>

<span class="hljs-strong">**Common API Import Fix Pattern:**</span>
<span class="hljs-code">```typescript
// ❌ BROKEN - Don't use:
import { authOptions } from '@/lib/auth';
import { prisma } from '@/lib/prisma'; 
import { requireAdmin } from '@/lib/admin';

// ✅ CORRECT - Use instead:
import { authOptions } from '../../auth/[...nextauth]/route';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

// Replace requireAdmin() with:
const user = await prisma.user.findUnique({
  where: { email: session.user.email },
  select: { role: true }
});
if (user?.role !== 'admin') {
  return NextResponse.json({ error: 'Admin required' }, { status: 403 });
}
```</span>

<span class="hljs-section">### Production Deployment Checklist</span>

<span class="hljs-bullet">1.</span> ✅ <span class="hljs-strong">**Database setup**</span>: Supabase project created and connection string copied
<span class="hljs-bullet">2.</span> ✅ <span class="hljs-strong">**Environment variables**</span>: All vars set in Vercel dashboard  
<span class="hljs-bullet">3.</span> ✅ <span class="hljs-strong">**Google OAuth**</span>: Production redirect URI added to Google Console
<span class="hljs-bullet">4.</span> ✅ <span class="hljs-strong">**Database migration**</span>: PageView table created via Supabase SQL Editor
<span class="hljs-bullet">5.</span> ✅ <span class="hljs-strong">**Admin access**</span>: Role set to 'admin' in User table for your email
<span class="hljs-bullet">6.</span> ✅ <span class="hljs-strong">**Vercel domain**</span>: Custom domain configured and aliased
<span class="hljs-bullet">7.</span> ✅ <span class="hljs-strong">**Test flow**</span>: Sign in → Upload CSV → Check dashboard → Visit data page → Verify analytics

<span class="hljs-section">### Getting Help</span>

<span class="hljs-strong">**Debugging Steps:**</span>
<span class="hljs-bullet">1.</span> <span class="hljs-strong">**Check browser console**</span> for client-side errors
<span class="hljs-bullet">2.</span> <span class="hljs-strong">**Check Vercel function logs**</span> for server-side errors  
<span class="hljs-bullet">3.</span> <span class="hljs-strong">**Test in incognito mode**</span> to rule out browser cache issues
<span class="hljs-bullet">4.</span> <span class="hljs-strong">**Verify database tables**</span> exist in Supabase Table Editor
<span class="hljs-bullet">5.</span> <span class="hljs-strong">**Check environment variables**</span> are set correctly in Vercel
<span class="hljs-bullet">6.</span> <span class="hljs-strong">**Test API endpoints**</span> directly with curl or Postman

<span class="hljs-strong">**Log Locations:**</span>
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Client errors**</span>: Browser DevTools → Console
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Server errors**</span>: Vercel Dashboard → Functions → View Function Logs
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Database errors**</span>: Supabase Dashboard → Logs
<span class="hljs-bullet">-</span> <span class="hljs-strong">**Build errors**</span>: Vercel Dashboard → Deployments → View Build Logs

<span class="hljs-section">## License</span>

MIT
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Master Claude Code Starter Prompt: For a Backend-First Content Generation API]]></title><description><![CDATA[🧠 Claude Code — Master Prompt (Backend-First GenAI API)

Role (System):
You are a senior backend architect + staff engineer. You ship production-grade Python with Django 5 + Django Ninja, clean architecture, high test coverage, and excellent docs. Y...]]></description><link>https://rcmisk.com/master-claude-code-starter-prompt-for-a-backend-first-content-generation-api</link><guid isPermaLink="true">https://rcmisk.com/master-claude-code-starter-prompt-for-a-backend-first-content-generation-api</guid><category><![CDATA[claude.ai]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[AI]]></category><category><![CDATA[#ai-tools]]></category><category><![CDATA[Python]]></category><category><![CDATA[Django]]></category><category><![CDATA[stripe]]></category><dc:creator><![CDATA[rcmisk]]></dc:creator><pubDate>Fri, 15 Aug 2025 04:11:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755231274315/8f3e2f5d-b533-4dc5-9bd4-708033441062.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<pre><code class="lang-markdown">🧠 Claude Code — Master Prompt (Backend-First GenAI API)

Role (System):
You are a senior backend architect + staff engineer. You ship production-grade Python with Django 5 + Django Ninja, clean architecture, high test coverage, and excellent docs. You do not reveal chain-of-thought; instead provide succinct reasoning summaries, concrete artifacts, and a running TODO log. Prefer explicit, verifiable code over abstractions. Target Python 3.11.

Prime Directive:

Break up and Plan this Project into organizable Milestones, broken out into Tasks with each Task tracked and each milestone tracked and shown to console when complete.

Build the backend only (no frontend) as a production-ready multi-tenant SaaS API.

Stack: Django 5, Django Ninja, PostgreSQL, Redis, PyJWT/SimpleJWT, httpx, pydantic/jsonschema, pytest + factory<span class="hljs-emphasis">_boy + coverage, stripe, resend, posthog, uvicorn/gunicorn. Always use industry standard opensource or free libraries or packages.

Auth: Google OAuth → JWT access/refresh in HTTP-only cookies (Secure, SameSite), CSRF for unsafe methods, strict CORS.

Per-user daily request limits by plan; each request returns exactly 10 items for X/Reddit.

Providers: OpenAI, Anthropic (Claude), Google (Gemini) via a pluggable adapter layer.

Dev: Docker + docker-compose (web, db, redis, worker). Prod: Google Cloud Run.

Docs: Stripe-style (dark/light), with examples in curl, httpie, Python, JavaScript, Java.

Tests for everything (auth, tenancy, quotas, webhooks, generator outputs, schema validation, security).

High-Level Goals

Social Generators now: X (Twitter) and Reddit.

Future Generators: Newsletter, Landing Page (returns JSON artifact; Next.js app code generation can be added later).

Provider-agnostic GenAI layer w/ strict JSON Schema validation.

Multi-tenant: org + membership + roles; all data org-scoped.

Billing: Stripe Checkout/Portal/Webhooks → plan → requests/day limit.

Emails: Resend. Analytics: PostHog.

Each API call (user/day) increments requests; X/Reddit responses always contain 10 items.

Exact Twitter Master Prompt (use verbatim) — name: tweet_</span>based<span class="hljs-emphasis">_on_</span>handle
Prompt: Mimic X Handle Tweet Style

Analyze the X (Twitter) handle [INSERT HANDLE] and identify the tone, voice, and stylistic patterns of their posts.

Then, create a series of [X] original tweets written in the same style.

Requirements:

Tweets should sound like [INSERT HANDLE] (tone, vocabulary, sentence rhythm).

Include the same balance of personal storytelling, reflections, and audience engagement that [INSERT HANDLE] typically uses.

Keep tweets under 280 characters.

Avoid direct copying; instead, generate new, original tweets that fit their voice.

Format each output as a standalone tweet, ready to post.

Example: If the handle is @robj3d3, the tweets should feel diary-like, candid, and self-reflective, often talking about building in public, life changes, and uncertainty.

Enforce server-side: X/Reddit always return 10 items per request in production.

Landing Page Generator — Output Schema (use verbatim)
{
  "landing<span class="hljs-emphasis">_page": {
    "name": "Generated Landing Page Name",
    "meta_</span>title": "SEO optimized title (max 60 chars)",
<span class="hljs-code">    "meta_description": "SEO optimized description (max 160 chars)",
    "sections": [
      {
        "section_type": "hero_section",
        "title": "Hero Section",
        "order": 0,
        "hero_data": {
          "headline": "Compelling main headline",
          "subtitle": "Supporting subtitle that explains the value proposition",
          "cta_button": "Primary Call to Action"
        }
      },
      {
        "section_type": "feature_section",
        "title": "Features Section",
        "order": 1,
        "features": [
          { "title": "Feature 1 Title", "description": "Detailed feature description and benefits", "icon": "🚀" },
          { "title": "Feature 2 Title", "description": "Another compelling feature description", "icon": "⚡" },
          { "title": "Feature 3 Title", "description": "Third feature that completes the value proposition", "icon": "🔒" }
        ]
      },
      {
        "section_type": "testimonials",
        "title": "Testimonials Section",
        "order": 2,
        "testimonials_data": {
          "section_title": "What Our Customers Say",
          "description": "Brief section description",
          "testimonials": [
            { "quote": "Authentic customer testimonial quote", "name": "Customer Name", "title": "Job Title", "company": "Company Name" },
            { "quote": "Second testimonial quote", "name": "Another Customer", "title": "Their Position", "company": "Their Company" }
          ]
        }
      },
      {
        "section_type": "pricing",
        "title": "Pricing Section",
        "order": 3,
        "pricing_data": {
          "section_title": "Choose Your Plan",
          "description": "Pricing section description",
          "plans": [
            { "name": "Free Plan", "subtitle": "Perfect for getting started", "price": "Free", "icon": "🆓", "descriptions": ["Basic Feature 1", "Basic Feature 2", "Community Support"] },
            { "name": "Pro Plan", "subtitle": "For growing businesses", "price": "$29", "icon": "⭐", "descriptions": ["All Free features", "Advanced Feature 1", "Priority Support", "Analytics Dashboard"] },
            { "name": "Enterprise", "subtitle": "For large organizations", "price": "$99", "icon": "🏢", "descriptions": ["All Pro features", "Custom Integrations", "Dedicated Support", "SLA Guarantee"] }
          ]
        }
      },
      {
        "section_type": "faq",
        "title": "FAQ Section",
        "order": 4,
        "faq_data": {
          "section_title": "Frequently Asked Questions",
          "description": "Find answers to common questions about our service",
          "faqs": [
            { "question": "How does it work?", "answer": "Detailed explanation of how the product or service works" },
            { "question": "What are the pricing options?", "answer": "Explanation of pricing plans and what's included" },
            { "question": "Is there a free trial?", "answer": "Information about trial options and getting started" },
            { "question": "How do I get support?", "answer": "Details about customer support and help resources" }
          ]
        }
      },
      {
        "section_type": "contact",
        "title": "Contact Form Section",
        "order": 5,
        "contact_form_data": {
          "form_title": "Get in Touch",
          "description": "Have questions? We'd love to hear from you. Send us a message and we'll respond as soon as possible.",
          "name_label": "Full Name",
          "email_label": "Email Address",
          "message_label": "Your Message",
          "submit_button_text": "Send Message"
        }
      },
      {
        "section_type": "newsletter",
        "title": "Newsletter Section",
        "order": 6,
        "newsletter_data": {
          "headline": "Stay Updated with Latest Features",
          "description": "Join our newsletter for product updates and exclusive tips",
          "placeholder_text": "Enter your email",
          "button_text": "Subscribe Now",
          "privacy_text": "We respect your privacy and never share your information"
        }
      }
    ]
  }
}
</span>

Content rules: real emoji (🚀 ⚡ 🔒 🤖 ⭐ 🏢), short prices (Free, $29, $99), headlines <span class="xml"><span class="hljs-tag">&lt; <span class="hljs-attr">60</span> <span class="hljs-attr">chars</span>, <span class="hljs-attr">meta</span> <span class="hljs-attr">title</span> &lt; <span class="hljs-attr">60</span>, <span class="hljs-attr">meta</span> <span class="hljs-attr">description</span> &lt; <span class="hljs-attr">160</span>, <span class="hljs-attr">conversion-focused</span> <span class="hljs-attr">tone.</span>

<span class="hljs-attr">Quotas</span> &amp; <span class="hljs-attr">Plans</span> (<span class="hljs-attr">per</span> <span class="hljs-attr">user</span>/<span class="hljs-attr">day</span>, <span class="hljs-attr">UTC</span>)

<span class="hljs-attr">FREE_REQUESTS_PER_DAY</span>=<span class="hljs-string">10</span> → ~<span class="hljs-attr">100</span> <span class="hljs-attr">items</span>/<span class="hljs-attr">day</span>

<span class="hljs-attr">INDY_REQUESTS_PER_DAY</span>=<span class="hljs-string">10</span> → ~<span class="hljs-attr">100</span> <span class="hljs-attr">items</span>/<span class="hljs-attr">day</span> ($<span class="hljs-attr">20</span>/<span class="hljs-attr">mo</span>)

<span class="hljs-attr">PRO_REQUESTS_PER_DAY</span>=<span class="hljs-string">100</span> → ~<span class="hljs-attr">1000</span> <span class="hljs-attr">items</span>/<span class="hljs-attr">day</span> ($<span class="hljs-attr">50</span>/<span class="hljs-attr">mo</span>)

<span class="hljs-attr">ITEMS_PER_REQUEST</span>=<span class="hljs-string">10</span> (<span class="hljs-attr">X</span>/<span class="hljs-attr">Reddit</span>)<span class="hljs-attr">.</span> <span class="hljs-attr">Landing</span> <span class="hljs-attr">Page</span> <span class="hljs-attr">returns</span> <span class="hljs-attr">1</span> <span class="hljs-attr">artifact</span> <span class="hljs-attr">but</span> <span class="hljs-attr">still</span> <span class="hljs-attr">counts</span> <span class="hljs-attr">as</span> <span class="hljs-attr">1</span> <span class="hljs-attr">request.</span>

<span class="hljs-attr">Repo</span> <span class="hljs-attr">Layout</span> (<span class="hljs-attr">backend</span> <span class="hljs-attr">only</span>)
<span class="hljs-attr">backend</span>/
├─ <span class="hljs-attr">manage.py</span>
├─ <span class="hljs-attr">pyproject.toml</span>  (<span class="hljs-attr">or</span> <span class="hljs-attr">requirements.txt</span>)
├─ <span class="hljs-attr">docker-compose.yml</span>
├─ <span class="hljs-attr">Dockerfile</span>
├─ <span class="hljs-attr">.env.example</span>
├─ <span class="hljs-attr">backend</span>/                 # <span class="hljs-attr">settings</span>, <span class="hljs-attr">urls</span>, <span class="hljs-attr">asgi</span>
│  ├─ <span class="hljs-attr">settings.py</span>
│  ├─ <span class="hljs-attr">urls.py</span>
│  └─ <span class="hljs-attr">asgi.py</span>
├─ <span class="hljs-attr">apps</span>/
│  ├─ <span class="hljs-attr">authx</span>/                # <span class="hljs-attr">google</span> <span class="hljs-attr">oauth</span>, <span class="hljs-attr">jwt</span> <span class="hljs-attr">cookies</span>, <span class="hljs-attr">csrf</span>
│  ├─ <span class="hljs-attr">orgs</span>/                 # <span class="hljs-attr">orgs</span>, <span class="hljs-attr">memberships</span>, <span class="hljs-attr">roles</span>
│  ├─ <span class="hljs-attr">billing</span>/              # <span class="hljs-attr">stripe</span>, <span class="hljs-attr">webhooks</span>
│  ├─ <span class="hljs-attr">usage</span>/                # <span class="hljs-attr">per-user</span> <span class="hljs-attr">daily</span> <span class="hljs-attr">requests</span>
│  ├─ <span class="hljs-attr">newsletter</span>/           # <span class="hljs-attr">subscribers</span>
│  ├─ <span class="hljs-attr">notifications</span>/        # <span class="hljs-attr">resend</span> <span class="hljs-attr">emails</span>
│  ├─ <span class="hljs-attr">analytics</span>/            # <span class="hljs-attr">posthog</span> <span class="hljs-attr">events</span>
│  └─ <span class="hljs-attr">social</span>/               # <span class="hljs-attr">routers</span> <span class="hljs-attr">for</span> /<span class="hljs-attr">social</span>/<span class="hljs-attr">x</span>, /<span class="hljs-attr">social</span>/<span class="hljs-attr">reddit</span>
├─ <span class="hljs-attr">social_media</span>/
│  ├─ <span class="hljs-attr">base</span>/                 # <span class="hljs-attr">shared</span> <span class="hljs-attr">interfaces</span>/<span class="hljs-attr">schemas</span>
│  ├─ <span class="hljs-attr">x</span>/                    # <span class="hljs-attr">TweetSet</span> <span class="hljs-attr">schema</span>, <span class="hljs-attr">prompt</span>, <span class="hljs-attr">adapter</span>
│  └─ <span class="hljs-attr">reddit</span>/               # <span class="hljs-attr">PostSet</span> <span class="hljs-attr">schema</span>, <span class="hljs-attr">adapter</span>
├─ <span class="hljs-attr">generators</span>/
│  ├─ <span class="hljs-attr">base</span>/                 # <span class="hljs-attr">provider-agnostic</span> <span class="hljs-attr">registry</span> &amp; <span class="hljs-attr">guards</span>
│  ├─ <span class="hljs-attr">social_x</span>/             # <span class="hljs-attr">wraps</span> <span class="hljs-attr">social_media</span>/<span class="hljs-attr">x</span>
│  ├─ <span class="hljs-attr">social_reddit</span>/        # <span class="hljs-attr">wraps</span> <span class="hljs-attr">social_media</span>/<span class="hljs-attr">reddit</span>
│  ├─ <span class="hljs-attr">newsletter</span>/           # (<span class="hljs-attr">future</span>)
│  └─ <span class="hljs-attr">landing_page</span>/         # (<span class="hljs-attr">future</span>) <span class="hljs-attr">JSON</span> <span class="hljs-attr">artifact</span>
├─ <span class="hljs-attr">providers</span>/
│  ├─ <span class="hljs-attr">openai</span>/
│  ├─ <span class="hljs-attr">anthropic</span>/
│  └─ <span class="hljs-attr">google</span>/
├─ <span class="hljs-attr">tests</span>/
└─ <span class="hljs-attr">README.md</span>

<span class="hljs-attr">Environment</span> (<span class="hljs-attr">.env.example</span>)
<span class="hljs-attr">DJANGO_SECRET_KEY</span>=
<span class="hljs-string">DJANGO_DEBUG</span>=<span class="hljs-string">false</span>
<span class="hljs-attr">ALLOWED_HOSTS</span>=<span class="hljs-string">localhost,127.0.0.1</span>

<span class="hljs-attr">DATABASE_URL</span>=<span class="hljs-string">postgres://postgres:postgres@db:5432/genai</span>
<span class="hljs-attr">REDIS_URL</span>=<span class="hljs-string">redis://redis:6379/0</span>

# <span class="hljs-attr">Google</span> <span class="hljs-attr">OAuth</span>
<span class="hljs-attr">GOOGLE_CLIENT_ID</span>=
<span class="hljs-string">GOOGLE_CLIENT_SECRET</span>=
<span class="hljs-string">GOOGLE_REDIRECT_URI</span>=<span class="hljs-string">http://localhost:8000/api/v1/auth/login/google/callback</span>

# <span class="hljs-attr">JWT</span>
<span class="hljs-attr">JWT_SIGNING_KEY</span>=
<span class="hljs-string">JWT_ACCESS_TTL</span>=<span class="hljs-string">900</span>
<span class="hljs-attr">JWT_REFRESH_TTL</span>=<span class="hljs-string">2592000</span>
<span class="hljs-attr">JWT_COOKIE_DOMAIN</span>=<span class="hljs-string">localhost</span>
<span class="hljs-attr">JWT_COOKIE_SECURE</span>=<span class="hljs-string">false</span>
<span class="hljs-attr">JWT_COOKIE_SAMESITE</span>=<span class="hljs-string">Lax</span>

# <span class="hljs-attr">Stripe</span>
<span class="hljs-attr">STRIPE_SECRET_KEY</span>=
<span class="hljs-string">STRIPE_WEBHOOK_SECRET</span>=
<span class="hljs-string">PRICE_INDY</span>=<span class="hljs-string">price_xxx</span>
<span class="hljs-attr">PRICE_PRO</span>=<span class="hljs-string">price_yyy</span>

# <span class="hljs-attr">Resend</span>
<span class="hljs-attr">RESEND_API_KEY</span>=
<span class="hljs-string">RESEND_FROM</span>=<span class="hljs-string">"GenAI &lt;hello@yourdomain.com&gt;</span></span></span>"

<span class="hljs-section"># PostHog</span>
POSTHOG<span class="hljs-emphasis">_KEY=
POSTHOG_</span>HOST=https://us.i.posthog.com

<span class="hljs-section"># Requests/day per user</span>
FREE<span class="hljs-emphasis">_REQUESTS_</span>PER<span class="hljs-emphasis">_DAY=10
INDY_</span>REQUESTS<span class="hljs-emphasis">_PER_</span>DAY=10
PRO<span class="hljs-emphasis">_REQUESTS_</span>PER<span class="hljs-emphasis">_DAY=100
ITEMS_</span>PER<span class="hljs-emphasis">_REQUEST=10

# X API
X_</span>API<span class="hljs-emphasis">_KEY=
X_</span>API<span class="hljs-emphasis">_SECRET=
X_</span>BEARER<span class="hljs-emphasis">_TOKEN=

# Reddit API
REDDIT_</span>CLIENT<span class="hljs-emphasis">_ID=
REDDIT_</span>CLIENT<span class="hljs-emphasis">_SECRET=
REDDIT_</span>USER<span class="hljs-emphasis">_AGENT=GenAI/1.0 by yourcompany

# Cloud Run
PORT=8000

API (Stripe-style docs)

Base URL: /api/v1
Headers: X-Org-Id: <span class="xml"><span class="hljs-tag"><span class="hljs-string">&lt;uuid&gt;</span></span></span>, X-CSRF-Token: <span class="xml"><span class="hljs-tag"><span class="hljs-string">&lt;token&gt;</span></span></span> (unsafe methods)
Cookies: access_</span>token, refresh<span class="hljs-emphasis">_token (HTTP-only)

GET /health → { "ok": true, "service": "genai-backend", "version": "v1" }

GET /usage/today → per-user request counters + plan

POST /social/x/generate → 10 tweets (uses tweet_</span>based<span class="hljs-emphasis">_on_</span>handle)

POST /social/reddit/generate → 10 posts

POST /content/landing-page/generate → 1 JSON artifact (schema above)

POST /billing/checkout → { url }

POST /billing/portal → { url }

POST /billing/webhook (raw body; verify signature)

Error shape

{
  "error": {
<span class="hljs-code">    "code": "DAILY_REQUEST_LIMIT_REACHED",
    "message": "Daily request limit reached (10/10).",
    "details": { "date":"2025-08-14","limit_requests":10,"used_requests":10,"items_per_request":10 }
  }
}
</span>

Code samples: include curl, httpie, Python (requests), JavaScript (fetch/Node), Java (OkHttp) for each endpoint (generate, usage, billing).

Testing

pytest + factory<span class="hljs-emphasis">_boy + coverage

Auth: OAuth callback → JWT cookies; refresh; logout; CSRF

Tenancy: org scoping enforced everywhere

Billing: webhook signature; price_</span>id → plan → requests/day mapping

Usage: atomic increments; UTC rollover; 429 when exceeded

Generators: schema validation; 10 items per call; invalid outputs rejected

Providers: OpenAI/Anthropic/Gemini adapters mocked &amp; unit tested

Security: CORS, cookie flags, input validation, error redaction

Docker &amp; Cloud Run

docker-compose up --build runs web + db + redis (+ worker if used).

Cloud Run: build + deploy

gcloud builds submit --tag gcr.io/$PROJECT<span class="hljs-emphasis">_ID/genai-api
gcloud run deploy genai-api --image gcr.io/$PROJECT_</span>ID/genai-api --region=$REGION \
  --allow-unauthenticated=false --set-env-vars="KEY=VALUE,..."

Deliverables (output now, in this order)

Reasoning Summary (≤6 sentences), TODO Log (~20 tasks), Milestones, Assumptions.

Repo scaffold + pyproject/requirements, settings, Dockerfile, docker-compose, .env.example.

Models &amp; migrations: UserProfile, Organization, Membership, Subscription, DailyUserRequests, SocialContentJob, NewsletterSubscriber, AuditEvent.

Auth endpoints (Google OAuth → JWT cookies; refresh; logout) + CSRF/CORS config + tests.

Billing (checkout, portal, webhook verified) + plan mapping to requests/day + tests.

Usage service (per-user/day, atomic) + middleware + tests.

Social: /social/x/generate (real adapter, schema, tests), /social/reddit/generate (schema, tests).

Content: /content/landing-page/generate (schema-validated JSON artifact) + tests.

Providers: OpenAI/Anthropic/Google adapters (config selectable) + tests.

Stripe-style API docs (dark/light) with code samples in curl/httpie/python/js/java.

Stop and wait for “Proceed to frontend” before writing any Next.js code.

Output Discipline

Produce real, runnable code and migrations—no placeholders.

Keep a TODO Log (YAML) and mark items ✅ when done.

Provide short reasoning summaries; no chain-of-thought.

If blocked, list up to 5 precise questions, then proceed with sensible defaults.

Begin.
</code></pre>
]]></content:encoded></item><item><title><![CDATA[How to Create a Dynamic Pricing Table for Your SaaS Application in Django]]></title><description><![CDATA[I’m building Ideaverify! Ideaverify will help automate idea validation for #indiehackers! Each user will be able to create a landing page tied to their own subdomain, with all the popular sections of your typical SaaS landing page. One section I thou...]]></description><link>https://rcmisk.com/how-to-create-a-dynamic-pricing-table-for-your-saas-application-in-django</link><guid isPermaLink="true">https://rcmisk.com/how-to-create-a-dynamic-pricing-table-for-your-saas-application-in-django</guid><category><![CDATA[Django]]></category><category><![CDATA[HTML5]]></category><category><![CDATA[SaaS]]></category><category><![CDATA[Build In Public]]></category><dc:creator><![CDATA[rcmisk]]></dc:creator><pubDate>Sat, 29 Jun 2024 21:25:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1719696000979/9df9d0ed-fe96-4782-bed9-14bd86fdb4ae.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I’m building Ideaverify! Ideaverify will help automate idea validation for #indiehackers! Each user will be able to create a landing page tied to their own subdomain, with all the popular sections of your typical SaaS landing page. One section I thought I’d share in public so others can learn along the way, is how to create a dynamic pricing table for their SaaS, and how to store and retrieve the data in Django.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/1*U7gGoYdLcCbYcBas81m_Pg.png" alt /></p>
<ol>
<li><p>You can use an existing django project and add the code below to it. Or start from scratch.</p>
</li>
<li><p>If starting from scratch: <code>cd Desktop</code></p>
</li>
<li><p>Make sure Django is installed. If not <code>pip install Django</code></p>
</li>
<li><p>Next <code>django-admin startproject landingpage</code></p>
</li>
<li><p><code>cd landingpage</code> and the below</p>
</li>
<li><p><code>pip install virtualenv</code></p>
</li>
<li><p><code>python3.8 -m venv env</code></p>
</li>
<li><p><code>source env/bin/activate</code></p>
</li>
<li><p><code>pip install Django</code></p>
</li>
<li><p>First let’s add the models in our <code>models.py</code>file.</p>
</li>
</ol>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> models

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PricingDescription</span>(<span class="hljs-params">models.Model</span>):</span>
    description = models.CharField(max_length=<span class="hljs-number">256</span>)
    order = models.PositiveIntegerField()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__str__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> self.description

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
        ordering = [<span class="hljs-string">"order"</span>, <span class="hljs-string">"pk"</span>]


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Pricing</span>(<span class="hljs-params">models.Model</span>):</span>
    <span class="hljs-comment"># payment interval enum</span>
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Interval</span>(<span class="hljs-params">models.IntegerChoices</span>):</span>
        MONTHLY = <span class="hljs-number">1</span>, <span class="hljs-string">"MONTHLY"</span>
        YEARLY = <span class="hljs-number">2</span>, <span class="hljs-string">"YEARLY"</span>

    <span class="hljs-comment"># usage type enum</span>
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Type</span>(<span class="hljs-params">models.IntegerChoices</span>):</span>
        RECURRING_PAYMENT = <span class="hljs-number">1</span>, <span class="hljs-string">"RecurringPayment"</span>
        ONE_TIME_PAYMENT = <span class="hljs-number">2</span>, <span class="hljs-string">"OneTimePayment"</span>

    name = models.CharField(max_length=<span class="hljs-number">256</span>)
    descriptions = models.ManyToManyField(
        to=PricingDescription, related_name=<span class="hljs-string">"pricing_description"</span>
    )
    price = models.PositiveIntegerField()
    interval = models.PositiveSmallIntegerField(
        choices=Interval.choices, null=<span class="hljs-literal">True</span>, blank=<span class="hljs-literal">True</span>
    )
    type = models.PositiveSmallIntegerField(
        choices=Type.choices, default=Type.RECURRING_PAYMENT
    )
    order = models.PositiveIntegerField()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__str__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> self.name

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
        ordering = [<span class="hljs-string">"order"</span>, <span class="hljs-string">"pk"</span>]

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LandingPage</span>(<span class="hljs-params">models.Model</span>):</span>
    primary_hero_title = models.CharField(max_length=<span class="hljs-number">256</span>)
    pricing = models.ManyToManyField(to=Pricing, related_name=<span class="hljs-string">"landingpage_pricing"</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__str__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> self.primary_hero_title
</code></pre>
<p>These 3 models will allow us to define the pricing data and associate it to a landing page.</p>
<p>Make sure to add folder <code>static_files</code> inside root directory.</p>
<p>Inside <code>static_files</code> add a <code>css</code> folder.</p>
<p>Inside your root directory add a <code>templates</code> directory.</p>
<p>Project structure should look like the below.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:659/1*yfZXh8iayvLn8sMSHXxWyg.png" alt /></p>
<p>Now run <code>python manange.py makemigrations</code> (should see <code>No changes detected</code> if this is a brand new project).</p>
<p>Now run <code>python manage.py migrate</code> .</p>
<p><img src="https://miro.medium.com/v2/resize:fit:549/1*FkOJJ2c2VJt0m9_iEQ7aKA.png" alt /></p>
<p>Now run <code>python manage.py makemigrations landingpage</code> and then <code>python manage.py migrate landingpage</code></p>
<p>2. Next let’s add some admin tables in our <code>admin.py</code>so we can add data through the UI.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.contrib <span class="hljs-keyword">import</span> admin
<span class="hljs-keyword">from</span> .models <span class="hljs-keyword">import</span> (
    LandingPage,
    Pricing,
    PricingDescription,
)

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LandingPageAdmin</span>(<span class="hljs-params">admin.ModelAdmin</span>):</span>
    model = LandingPage
    list_display = (
        <span class="hljs-string">"primary_hero_title"</span>,
    )


admin.site.register(LandingPage, LandingPageAdmin)

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PricingAdmin</span>(<span class="hljs-params">admin.ModelAdmin</span>):</span>
    model = Pricing
    list_display = (<span class="hljs-string">"id"</span>, <span class="hljs-string">"name"</span>)

admin.site.register(Pricing, PricingAdmin)

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PricingDescriptionAdmin</span>(<span class="hljs-params">admin.ModelAdmin</span>):</span>
    model = PricingDescription
    list_display = (<span class="hljs-string">"id"</span>, <span class="hljs-string">"description"</span>)

admin.site.register(PricingDescription, PricingDescriptionAdmin)
</code></pre>
<p>3. Now let’s add to our views.py.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.shortcuts <span class="hljs-keyword">import</span> render
<span class="hljs-keyword">from</span> .models <span class="hljs-keyword">import</span> LandingPage

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span>(<span class="hljs-params">request</span>):</span>
    landing_page = LandingPage.objects.all()[<span class="hljs-number">0</span>] <span class="hljs-comment"># assuming you have 1 landing page.</span>
    context = {<span class="hljs-string">"landing_page"</span>: landing_page}

    <span class="hljs-keyword">return</span> render(request, <span class="hljs-string">"index.html"</span>, context)
</code></pre>
<p>4. Now add an <code>index.html</code> inside your <code>templates</code> folder if not already created with some html code like so.</p>
<pre><code class="lang-python">{% load static %}
&lt;!DOCTYPE html&gt;
&lt;html&gt;

&lt;head&gt;
    &lt;meta charset=<span class="hljs-string">"utf-8"</span> /&gt;
    &lt;title&gt;landingpage&lt;/title&gt;
    &lt;meta content=<span class="hljs-string">"width=device-width, initial-scale=1"</span> name=<span class="hljs-string">"viewport"</span> /&gt;
    &lt;link href=<span class="hljs-string">"{% static 'css/landingpage.css' %}"</span> rel=<span class="hljs-string">"stylesheet"</span>
        type=<span class="hljs-string">"text/css"</span> /&gt;
&lt;/head&gt;

&lt;body&gt;
    &lt;section <span class="hljs-class"><span class="hljs-keyword">class</span>="<span class="hljs-title">rl_section_pricing18</span>"&gt;
        &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl</span>-<span class="hljs-title">padding</span>-<span class="hljs-title">global</span>-2"&gt;
            &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl</span>-<span class="hljs-title">container</span>-<span class="hljs-title">large</span>-2"&gt;
                &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl</span>-<span class="hljs-title">padding</span>-<span class="hljs-title">section</span>-<span class="hljs-title">large</span>-2"&gt;
                    &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl_pricing18_component</span>"&gt;
                        &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl_pricing18_heading</span>-<span class="hljs-title">wrapper</span>"&gt;
                            &lt;<span class="hljs-title">h2</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl</span>-<span class="hljs-title">heading</span>-<span class="hljs-title">style</span>-<span class="hljs-title">h2</span>"&gt;<span class="hljs-title">Pricing</span> <span class="hljs-title">plan</span>&lt;/<span class="hljs-title">h2</span>&gt;
                        &lt;/<span class="hljs-title">div</span>&gt;
                        &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl_pricing18_spacing</span>-<span class="hljs-title">block</span>-3"&gt;&lt;/<span class="hljs-title">div</span>&gt;
                        &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl_pricing18_plans_</span>{{<span class="hljs-title">landing_page</span>.<span class="hljs-title">pricing</span>.<span class="hljs-title">all</span>|<span class="hljs-title">length</span>}}"&gt;
                            {% <span class="hljs-title">for</span> <span class="hljs-title">price</span> <span class="hljs-title">in</span> <span class="hljs-title">landing_page</span>.<span class="hljs-title">pricing</span>.<span class="hljs-title">all</span> %}
                            &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl_pricing18_plan</span>"&gt;
                                &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl_pricing18_plan</span>-<span class="hljs-title">content</span>"&gt;
                                    &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl_pricing18_plan</span>-<span class="hljs-title">content</span>-<span class="hljs-title">top</span>"&gt;
                                        &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl_pricing18_price</span>-<span class="hljs-title">wrapper</span>"&gt;
                                            &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl</span>-<span class="hljs-title">heading</span>-<span class="hljs-title">style</span>-<span class="hljs-title">h6</span>"&gt;{{<span class="hljs-title">price</span>.<span class="hljs-title">name</span>}}&lt;/<span class="hljs-title">div</span>&gt;
                                            &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl_pricing18_spacing</span>-<span class="hljs-title">block</span>-4"&gt;&lt;/<span class="hljs-title">div</span>&gt;
                                            &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl</span>-<span class="hljs-title">heading</span>-<span class="hljs-title">style</span>-<span class="hljs-title">h1</span>-2"&gt;${{<span class="hljs-title">price</span>.<span class="hljs-title">price</span>}}&lt;<span class="hljs-title">span</span>
                                                    <span class="hljs-title">class</span>="<span class="hljs-title">rl</span>-<span class="hljs-title">heading</span>-<span class="hljs-title">style</span>-<span class="hljs-title">h4</span>"&gt;/{% <span class="hljs-title">if</span> <span class="hljs-title">price</span>.<span class="hljs-title">interval</span> == 1 %}<span class="hljs-title">mo</span>
                                                    {% <span class="hljs-title">else</span>%}<span class="hljs-title">yr</span>{% <span class="hljs-title">endif</span> %}&lt;/<span class="hljs-title">span</span>&gt;&lt;/<span class="hljs-title">div</span>&gt;
                                            &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl_pricing18_spacing</span>-<span class="hljs-title">block</span>-4"&gt;&lt;/<span class="hljs-title">div</span>&gt;
                                        &lt;/<span class="hljs-title">div</span>&gt;
                                        &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl_pricing18_spacing</span>-<span class="hljs-title">block</span>-5"&gt;&lt;/<span class="hljs-title">div</span>&gt;
                                        &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl_pricing18_feature</span>-<span class="hljs-title">list</span>"&gt;
                                            {% <span class="hljs-title">for</span> <span class="hljs-title">description</span> <span class="hljs-title">in</span> <span class="hljs-title">price</span>.<span class="hljs-title">descriptions</span>.<span class="hljs-title">all</span> %}
                                            &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl_pricing18_feature</span>"&gt;
                                                &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl_pricing18_icon</span>-<span class="hljs-title">wrapper</span>"&gt;
                                                    &lt;<span class="hljs-title">div</span> <span class="hljs-title">class</span>="<span class="hljs-title">rl_pricing18_icon</span> <span class="hljs-title">w</span>-<span class="hljs-title">embed</span>"&gt;&lt;<span class="hljs-title">svg</span> <span class="hljs-title">fill</span>="<span class="hljs-title">none</span>"
                                                            <span class="hljs-title">height</span>=" 100%" <span class="hljs-title">viewbox</span>="0 0 24 24" <span class="hljs-title">width</span>=" 100%"
                                                            <span class="hljs-title">xmlns</span>="<span class="hljs-title">http</span>:</span>//www.w3.org/<span class="hljs-number">2000</span>/svg<span class="hljs-string">"&gt;
                                                            &lt;path
                                                                d="</span>M19<span class="hljs-number">.8501</span> <span class="hljs-number">7.25012L</span>9<span class="hljs-number">.2501</span> <span class="hljs-number">17.8501</span>C9<span class="hljs-number">.15621</span> <span class="hljs-number">17.9448</span> <span class="hljs-number">9.02842</span> <span class="hljs-number">17.998</span> <span class="hljs-number">8.8951</span> <span class="hljs-number">17.998</span>C8<span class="hljs-number">.76178</span> <span class="hljs-number">17.998</span> <span class="hljs-number">8.63398</span> <span class="hljs-number">17.9448</span> <span class="hljs-number">8.5401</span> <span class="hljs-number">17.8501L</span>3<span class="hljs-number">.1501</span> <span class="hljs-number">12.4601</span>C3<span class="hljs-number">.05544</span> <span class="hljs-number">12.3662</span> <span class="hljs-number">3.0022</span> <span class="hljs-number">12.2384</span> <span class="hljs-number">3.0022</span> <span class="hljs-number">12.1051</span>C3<span class="hljs-number">.0022</span> <span class="hljs-number">11.9718</span> <span class="hljs-number">3.05544</span> <span class="hljs-number">11.844</span> <span class="hljs-number">3.1501</span> <span class="hljs-number">11.7501L</span>3<span class="hljs-number">.8501</span> <span class="hljs-number">11.0501</span>C3<span class="hljs-number">.94398</span> <span class="hljs-number">10.9555</span> <span class="hljs-number">4.07178</span> <span class="hljs-number">10.9022</span> <span class="hljs-number">4.2051</span> <span class="hljs-number">10.9022</span>C4<span class="hljs-number">.33842</span> <span class="hljs-number">10.9022</span> <span class="hljs-number">4.46621</span> <span class="hljs-number">10.9555</span> <span class="hljs-number">4.5601</span> <span class="hljs-number">11.0501L</span>8<span class="hljs-number">.8901</span> <span class="hljs-number">15.3801L</span>18<span class="hljs-number">.4401</span> <span class="hljs-number">5.83012</span>C18<span class="hljs-number">.6379</span> <span class="hljs-number">5.63833</span> <span class="hljs-number">18.9523</span> <span class="hljs-number">5.63833</span> <span class="hljs-number">19.1501</span> <span class="hljs-number">5.83012L</span>19<span class="hljs-number">.8501</span> <span class="hljs-number">6.54012</span>C19<span class="hljs-number">.9448</span> <span class="hljs-number">6.634</span> <span class="hljs-number">19.998</span> <span class="hljs-number">6.7618</span> <span class="hljs-number">19.998</span> <span class="hljs-number">6.89512</span>C19<span class="hljs-number">.998</span> <span class="hljs-number">7.02844</span> <span class="hljs-number">19.9448</span> <span class="hljs-number">7.15623</span> <span class="hljs-number">19.8501</span> <span class="hljs-number">7.25012</span>Z<span class="hljs-string">"
                                                                fill="</span>currentColo<span class="hljs-string">r"&gt;&lt;/path&gt;
                                                        &lt;/svg&gt;&lt;/div&gt;
                                                &lt;/div&gt;
                                                &lt;div class="</span>rl-text-style-regular<span class="hljs-number">-2</span><span class="hljs-string">"&gt;{{ description.description }}&lt;/div&gt;
                                            &lt;/div&gt;
                                            {% endfor %}
                                        &lt;/div&gt;
                                        &lt;div class="</span>rl_pricing18_spacing-block<span class="hljs-number">-6</span><span class="hljs-string">"&gt;&lt;/div&gt;
                                    &lt;/div&gt;
                                    &lt;a class="</span>rl-button<span class="hljs-number">-2</span> w-button<span class="hljs-string">" href="</span><span class="hljs-comment">#"&gt;Get started&lt;/a&gt;</span>
                                &lt;/div&gt;
                            &lt;/div&gt;
                            {% endfor %}
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/section&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>5. Now let’s add some css to <code>landingpage.css</code> inside your <code>static_files/css</code></p>
<pre><code class="lang-python">.rl-button<span class="hljs-number">-2</span> {
    border: <span class="hljs-number">1</span>px solid black;
    background-color: black;
    color: white;
    text-align: center;
    padding: <span class="hljs-number">0.75</span>rem <span class="hljs-number">1.5</span>rem;
    font-family: system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
      Noto Sans, Ubuntu, Cantarell, Helvetica Neue, Oxygen, Fira Sans, Droid Sans,
      sans-serif;
    font-size: <span class="hljs-number">1</span>rem;
}
.rl-text-style-regular<span class="hljs-number">-2</span> {
    color: black;
    margin-top: <span class="hljs-number">0</span>;
    margin-bottom: <span class="hljs-number">0</span>;
    font-family: system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
      Noto Sans, Ubuntu, Cantarell, Helvetica Neue, Oxygen, Fira Sans, Droid Sans,
      sans-serif;
    font-size: <span class="hljs-number">1</span>rem;
    font-weight: <span class="hljs-number">400</span>;
    line-height: <span class="hljs-number">1.5</span>;
}
.rl-heading-style-h4 {
    color: black;
    margin-top: <span class="hljs-number">0</span>;
    margin-bottom: <span class="hljs-number">0</span>;
    font-family: system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
      Noto Sans, Ubuntu, Cantarell, Helvetica Neue, Oxygen, Fira Sans, Droid Sans,
      sans-serif;
    font-size: <span class="hljs-number">2</span>rem;
    font-weight: <span class="hljs-number">700</span>;
    line-height: <span class="hljs-number">1.3</span>;
}

.rl-heading-style-h1<span class="hljs-number">-2</span> {
    color: black;
    margin-top: <span class="hljs-number">0</span>;
    margin-bottom: <span class="hljs-number">0</span>;
    font-family: system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
      Noto Sans, Ubuntu, Cantarell, Helvetica Neue, Oxygen, Fira Sans, Droid Sans,
      sans-serif;
    font-size: <span class="hljs-number">3.5</span>rem;
    font-weight: <span class="hljs-number">700</span>;
    line-height: <span class="hljs-number">1.2</span>;
}

.rl-heading-style-h6 {
    color: black;
    margin-top: <span class="hljs-number">0</span>;
    margin-bottom: <span class="hljs-number">0</span>;
    font-family: system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
      Noto Sans, Ubuntu, Cantarell, Helvetica Neue, Oxygen, Fira Sans, Droid Sans,
      sans-serif;
    font-size: <span class="hljs-number">1.25</span>rem;
    font-weight: <span class="hljs-number">700</span>;
    line-height: <span class="hljs-number">1.4</span>;
}
.rl-heading-style-h2 {
    color: black;
    margin-top: <span class="hljs-number">0</span>;
    margin-bottom: <span class="hljs-number">0</span>;
    font-family: system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
      Noto Sans, Ubuntu, Cantarell, Helvetica Neue, Oxygen, Fira Sans, Droid Sans,
      sans-serif;
    font-size: <span class="hljs-number">3</span>rem;
    font-weight: <span class="hljs-number">700</span>;
    line-height: <span class="hljs-number">1.2</span>;
}

.rl-padding-section-large<span class="hljs-number">-2</span> {
  padding-top: <span class="hljs-number">7</span>rem;
  padding-bottom: <span class="hljs-number">7</span>rem;
}

.rl-container-large<span class="hljs-number">-2</span> {
  width: <span class="hljs-number">100</span>%;
  max-width: <span class="hljs-number">80</span>rem;
  margin-left: auto;
  margin-right: auto;
}

.rl-padding-<span class="hljs-keyword">global</span><span class="hljs-number">-2</span> {
  padding-left: <span class="hljs-number">5</span>%;
  padding-right: <span class="hljs-number">5</span>%;
}

.rl_pricing18_spacing-block<span class="hljs-number">-6</span> {
  width: <span class="hljs-number">100</span>%;
  padding-bottom: <span class="hljs-number">2</span>rem;
}

.rl_pricing18_icon {
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: <span class="hljs-number">1.5</span>rem;
  height: <span class="hljs-number">1.5</span>rem;
  display: flex;
}

.rl_pricing18_icon-wrapper {
  color: black;
  flex: none;
  align-self: flex-start;
}

.rl_pricing18_feature {
  grid-column-gap: <span class="hljs-number">1</span>rem;
  grid-row-gap: <span class="hljs-number">1</span>rem;
  display: flex;
}

.rl_pricing18_feature-list {
  grid-column-gap: <span class="hljs-number">1</span>rem;
  grid-row-gap: <span class="hljs-number">1</span>rem;
  grid-template-rows: auto;
  grid-template-columns: <span class="hljs-number">1</span>fr;
  grid-auto-columns: <span class="hljs-number">1</span>fr;
  padding-top: <span class="hljs-number">0.5</span>rem;
  padding-bottom: <span class="hljs-number">0.5</span>rem;
  display: grid;
}

.rl_pricing18_spacing-block<span class="hljs-number">-5</span> {
  width: <span class="hljs-number">100</span>%;
  padding-bottom: <span class="hljs-number">2</span>rem;
}

.rl_pricing18_spacing-block<span class="hljs-number">-4</span> {
  width: <span class="hljs-number">100</span>%;
  padding-bottom: <span class="hljs-number">0.5</span>rem;
}

.rl_pricing18_price-wrapper {
  text-align: center;
}

.rl_pricing18_plan-content {
  flex-direction: column;
  justify-content: space-between;
  height: <span class="hljs-number">100</span>%;
  display: flex;
}

.rl_pricing18_plan {
  border: <span class="hljs-number">1</span>px solid black;
  flex-direction: column;
  padding: <span class="hljs-number">2</span>rem;
  display: flex;
}

.rl_pricing18_plans_3 {
  grid-column-gap: <span class="hljs-number">2</span>rem;
  grid-row-gap: <span class="hljs-number">2</span>rem;
  grid-template-rows: auto;
  grid-template-columns: <span class="hljs-number">1</span>fr <span class="hljs-number">1</span>fr <span class="hljs-number">1</span>fr;
  grid-auto-columns: <span class="hljs-number">1</span>fr;
  width: <span class="hljs-number">100</span>%;
  display: grid;
}

.rl_pricing18_plans_2 {
  grid-column-gap: <span class="hljs-number">2</span>rem;
  grid-row-gap: <span class="hljs-number">2</span>rem;
  grid-template-rows: auto;
  grid-template-columns: <span class="hljs-number">1</span>fr <span class="hljs-number">1</span>fr;
  grid-auto-columns: <span class="hljs-number">1</span>fr;
  width: <span class="hljs-number">100</span>%;
  display: grid;
}

.rl_pricing18_spacing-block<span class="hljs-number">-3</span> {
  width: <span class="hljs-number">100</span>%;
  padding-bottom: <span class="hljs-number">5</span>rem;
}

.rl_pricing18_spacing-block<span class="hljs-number">-2</span> {
  width: <span class="hljs-number">100</span>%;
  padding-bottom: <span class="hljs-number">1.5</span>rem;
}

.rl_pricing18_spacing-block<span class="hljs-number">-1</span> {
  width: <span class="hljs-number">100</span>%;
  padding-bottom: <span class="hljs-number">1</span>rem;
}

.rl_pricing18_heading-wrapper {
  text-align: center;
  width: <span class="hljs-number">100</span>%;
  max-width: <span class="hljs-number">48</span>rem;
}

.rl_pricing18_component {
  flex-direction: column;
  align-items: center;
  display: flex;
}

<span class="hljs-meta">@media screen and (max-width: 991px) {</span>
  .rl-heading-style-h4 {
    font-size: <span class="hljs-number">1.75</span>rem;
  }
  .rl-heading-style-h1<span class="hljs-number">-2</span> {
    font-size: <span class="hljs-number">3.25</span>rem;
  }
  .rl_pricing18_plans_1 {
    grid-template-columns: <span class="hljs-number">1</span>fr;
  }

  .rl_pricing18_spacing-block<span class="hljs-number">-3</span> {
    padding-bottom: <span class="hljs-number">4.5</span>rem;
  }
  .rl-heading-style-h2 {
    font-size: <span class="hljs-number">2.75</span>rem;
  }
  .rl-padding-section-large<span class="hljs-number">-2</span> {
    padding-top: <span class="hljs-number">6</span>rem;
    padding-bottom: <span class="hljs-number">6</span>rem;
  }
}

<span class="hljs-meta">@media screen and (max-width: 767px) {</span>
  .rl-heading-style-h4 {
    font-size: <span class="hljs-number">1.5</span>rem;
    line-height: <span class="hljs-number">1.4</span>;
  }
  .rl-heading-style-h1<span class="hljs-number">-2</span> {
    font-size: <span class="hljs-number">2.5</span>rem;
  }
  .rl-padding-section-large<span class="hljs-number">-2</span> {
    padding-top: <span class="hljs-number">4</span>rem;
    padding-bottom: <span class="hljs-number">4</span>rem;
  }
  .rl_pricing18_spacing-block<span class="hljs-number">-6</span>,
  .rl_pricing18_spacing-block<span class="hljs-number">-5</span> {
    padding-bottom: <span class="hljs-number">1.5</span>rem;
  }
  .rl_pricing18_plan {
    padding-left: <span class="hljs-number">1.5</span>rem;
    padding-right: <span class="hljs-number">1.5</span>rem;
  }

  .rl_pricing18_plans_1 {
    grid-template-columns: <span class="hljs-number">1</span>fr;
  }

  .rl_pricing18_spacing-block<span class="hljs-number">-3</span> {
    padding-bottom: <span class="hljs-number">3</span>rem;
  }
  .rl_pricing18_spacing-block<span class="hljs-number">-2</span> {
    padding-bottom: <span class="hljs-number">1.25</span>rem;
  }
  .rl_pricing18_spacing-block<span class="hljs-number">-1</span> {
    padding-bottom: <span class="hljs-number">0.75</span>rem;
  }
  .rl-heading-style-h2 {
    font-size: <span class="hljs-number">2.25</span>rem;
  }
}
</code></pre>
<p>6. Now let’s add a <code>urls.py</code> with the below.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.conf <span class="hljs-keyword">import</span> settings
<span class="hljs-keyword">from</span> django.conf.urls.static <span class="hljs-keyword">import</span> static
<span class="hljs-keyword">from</span> django.contrib <span class="hljs-keyword">import</span> admin
<span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> path


<span class="hljs-keyword">from</span> .views <span class="hljs-keyword">import</span> *

urlpatterns = [
    path(<span class="hljs-string">"admin/"</span>, admin.site.urls),
    path(<span class="hljs-string">"index/"</span>, index),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
</code></pre>
<p>7. Make sure your <code>settings.py</code> has been set up and added.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> pathlib <span class="hljs-keyword">import</span> Path

<span class="hljs-comment">#########################################</span>
<span class="hljs-comment">##  SITE_NAME - change to your own ##</span>
<span class="hljs-comment">#########################################</span>
SITE_NAME = <span class="hljs-string">"Landing page"</span>

<span class="hljs-comment"># Build paths inside the project like this: os.path.join(BASE_DIR, ...)</span>
BASE_DIR = Path(__file__).resolve().parent.parent
<span class="hljs-comment"># SECURITY WARNING: keep the secret key used in production secret!</span>
SECRET_KEY = <span class="hljs-string">"your_secret_key"</span>

<span class="hljs-comment"># SECURITY WARNING: don't run with debug turned on in production!</span>
DEBUG = <span class="hljs-literal">True</span>
<span class="hljs-comment">#########################################</span>
<span class="hljs-comment">##  Application Definition ##</span>
<span class="hljs-comment">#########################################</span>

INSTALLED_APPS = [
    <span class="hljs-string">"django.contrib.admin"</span>,
    <span class="hljs-string">"django.contrib.auth"</span>,
    <span class="hljs-string">"django.contrib.contenttypes"</span>,
    <span class="hljs-string">"django.contrib.sessions"</span>,
    <span class="hljs-string">"django.contrib.messages"</span>,
    <span class="hljs-string">"django.contrib.staticfiles"</span>,
    <span class="hljs-string">"landingpage"</span>,
]

MIDDLEWARE = [
    <span class="hljs-string">'django.middleware.security.SecurityMiddleware'</span>,
    <span class="hljs-string">'django.contrib.sessions.middleware.SessionMiddleware'</span>,
    <span class="hljs-string">'django.middleware.common.CommonMiddleware'</span>,
    <span class="hljs-string">'django.middleware.csrf.CsrfViewMiddleware'</span>,
    <span class="hljs-string">'django.contrib.auth.middleware.AuthenticationMiddleware'</span>,
    <span class="hljs-string">'django.contrib.messages.middleware.MessageMiddleware'</span>,
    <span class="hljs-string">'django.middleware.clickjacking.XFrameOptionsMiddleware'</span>,
]

ROOT_URLCONF = <span class="hljs-string">"landingpage.urls"</span>

TEMPLATES = [
    {
        <span class="hljs-string">"BACKEND"</span>: <span class="hljs-string">"django.template.backends.django.DjangoTemplates"</span>,
        <span class="hljs-string">"DIRS"</span>: [
            BASE_DIR,
            <span class="hljs-string">"templates/"</span>,
        ],
        <span class="hljs-string">"APP_DIRS"</span>: <span class="hljs-literal">True</span>,
        <span class="hljs-string">"OPTIONS"</span>: {
            <span class="hljs-string">"context_processors"</span>: [
                <span class="hljs-string">"django.template.context_processors.debug"</span>,
                <span class="hljs-string">"django.template.context_processors.request"</span>,
                <span class="hljs-string">"django.contrib.auth.context_processors.auth"</span>,
                <span class="hljs-string">"django.contrib.messages.context_processors.messages"</span>,
            ],
        },
    },
]

WSGI_APPLICATION = <span class="hljs-string">"landingpage.wsgi.application"</span>


<span class="hljs-comment">#########################################</span>
<span class="hljs-comment">##  Database ##</span>
<span class="hljs-comment">#########################################</span>
DATABASES = {
    <span class="hljs-string">'default'</span>: {
        <span class="hljs-string">'ENGINE'</span>: <span class="hljs-string">'django.db.backends.sqlite3'</span>,
        <span class="hljs-string">'NAME'</span>: os.path.join(BASE_DIR, <span class="hljs-string">'db.sqlite3'</span>),
    }
}

<span class="hljs-comment">#########################################</span>
<span class="hljs-comment">##  Password Validation ##</span>
<span class="hljs-comment">#########################################</span>

AUTH_PASSWORD_VALIDATORS = [
    {
        <span class="hljs-string">"NAME"</span>: <span class="hljs-string">"django.contrib.auth.password_validation.UserAttributeSimilarityValidator"</span>,
    },
    {
        <span class="hljs-string">"NAME"</span>: <span class="hljs-string">"django.contrib.auth.password_validation.MinimumLengthValidator"</span>,
    },
    {
        <span class="hljs-string">"NAME"</span>: <span class="hljs-string">"django.contrib.auth.password_validation.CommonPasswordValidator"</span>,
    },
    {
        <span class="hljs-string">"NAME"</span>: <span class="hljs-string">"django.contrib.auth.password_validation.NumericPasswordValidator"</span>,
    },
]

<span class="hljs-comment">#########################################</span>
<span class="hljs-comment">##  Internationalization ##</span>
<span class="hljs-comment">#########################################</span>

LANGUAGE_CODE = <span class="hljs-string">"en-us"</span>

TIME_ZONE = <span class="hljs-string">"America/New_York"</span>

USE_I18N = <span class="hljs-literal">True</span>

USE_L10N = <span class="hljs-literal">True</span>

USE_TZ = <span class="hljs-literal">True</span>

DEFAULT_AUTO_FIELD = <span class="hljs-string">"django.db.models.BigAutoField"</span>

STATIC_ROOT = os.path.join(BASE_DIR, <span class="hljs-string">"staticfiles"</span>)
STATIC_URL = BACKEND_URL + <span class="hljs-string">"/static/"</span>
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, <span class="hljs-string">"static_files"</span>),
]
</code></pre>
<p>8. Now run <code>python manage.py createsuperuser</code> and fill in your username, email, and password to sign in to your Django Admin UI</p>
<p>9. Run <code>python manage.py runserver</code></p>
<p>10. Go to <a target="_blank" href="http://127.0.0.1:8000/admin/">http://127.0.0.1:8000/admin/</a></p>
<p>11. Log in with your username and password.</p>
<p>12. Go to <a target="_blank" href="http://127.0.0.1:8000/admin/landingpage/pricingdescription/">http://127.0.0.1:8000/admin/landingpage/pricingdescription/</a></p>
<p>13. Add a description for Basic, Pro and Premium.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/1*uc6OUQsH6R1c-k9hiYPmAg.png" alt /></p>
<p>14. Go to <a target="_blank" href="http://127.0.0.1:8000/admin/landingpage/pricing/">http://127.0.0.1:8000/admin/landingpage/pricing/</a></p>
<p>15. Add 3 Prices with your descriptions. One for Basic, Pro, and Premium.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/1*I9j9XIOWFnvZUuYfJ6KaEw.png" alt /></p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/1*aBy9PHA6kjbnXbt6wSeUcQ.png" alt /></p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/1*2Az7hcoPr94gTyTS8If6zA.png" alt /></p>
<p>Your pricing table should have 3 prices now.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/1*4WsDKEDAnqNbylhOggilSg.png" alt /></p>
<p>16. Now go to <a target="_blank" href="http://127.0.0.1:8000/admin/landingpage/landingpage/">http://127.0.0.1:8000/admin/landingpage/landingpage/</a> and add a landing page.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/1*tRD-xL6FlH3c_HoJGZumeQ.png" alt /></p>
<p>17. You can now view the dynamic Pricing table by going to <a target="_blank" href="http://localhost:8000/index/">http://localhost:8000/index/</a></p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/1*sWUhogdByyVDgcx7KzfKLw.png" alt /></p>
<p>That’s it! Hope you enjoyed and found this helpful.</p>
<p>If you liked this tutorial and you enjoy learning about coding, building in public, indiehackers, automation, start ups or entrepreneurship, follow me here:</p>
<p><a target="_blank" href="https://rcmisk.com/">rcmisk.com</a></p>
<p><a target="_blank" href="https://twitter.com/rcmisk">https://twitter.com/rcmisk</a></p>
<p><a target="_blank" href="https://www.indiehackers.com/rcmisk">https://indiehackers.com/rcmisk</a></p>
<p><a target="_blank" href="https://dev.to/rcmisk">https://dev.to/rcmisk</a></p>
<p><a target="_blank" href="https://medium.com/@rcmisk">https://medium.com/@rcmisk</a></p>
<p>Sign up for my <a target="_blank" href="https://rcmisk.com/newsletter">newsletter</a> to keep track of my progress and to learn along the way!</p>
]]></content:encoded></item><item><title><![CDATA[Webflow to Django: Template Preparation Guide]]></title><description><![CDATA[I love Webflow for designing UI, and I love Django for developing REST API’s. I wanted to try combining the two.
I have a project in Django where I give the user the ability to create their own SaaS landing page based off a set of template choices (a...]]></description><link>https://rcmisk.com/webflow-to-django-template-preparation-guide</link><guid isPermaLink="true">https://rcmisk.com/webflow-to-django-template-preparation-guide</guid><category><![CDATA[Django]]></category><category><![CDATA[webflow]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[rcmisk]]></dc:creator><pubDate>Wed, 29 May 2024 02:44:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/XJXWbfSo2f0/upload/fdaff82ebb86cff8ae50a240aa7441bd.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I love Webflow for designing UI, and I love Django for developing REST API’s. I wanted to try combining the two.</p>
<p>I have a project in Django where I give the user the ability to create their own SaaS landing page based off a set of template choices (all designed in Webflow).</p>
<p>In order to design your landing page in Webflow and then export and bring into Django you need to first hit the export code button in Webflow like so:</p>
<ol>
<li>First hit button in the top right corner as shown below:</li>
</ol>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/1*RbMr65bfDPDd-XhhW0BCqw.png" alt /></p>
<p>2. Then hit prepare zip and download the files as shown below:</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/1*MEDzf9qfv1Vq9mcOD9pV6Q.png" alt /></p>
<p>3. Now you have all of your files from you landing page on your local machine.</p>
<p>4. Next, copy the folder into a location inside your Django project. I made a directory called <code>webflow-templates</code> and placed the files and folders (index.html, css, js, images) into another folder called <code>landingpage_option1</code></p>
<p>5. Make sure this folder <code>landingpage_option1</code> is referenced in the scripts below.</p>
<p>6. On my local machine I then <code>pip install beautifulsoup4</code></p>
<p>7. This is used in the scripts to parse the html files.</p>
<p>8. I then created a <code>scripts</code> folder that contains 2 python scripts which are below.</p>
<p>9. Run <code>move_exported_dirs_</code><a target="_blank" href="http://files.py"><code>files.py</code></a></p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/env python3.8.3</span>
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> shutil

<span class="hljs-comment"># moving html files into templates</span>
target_dir = <span class="hljs-string">"../templates/landingpage_option1/"</span>
source_dir = <span class="hljs-string">"../webflow-templates/landingpage_option1"</span>
print(<span class="hljs-string">"Source: "</span> + source_dir)
files = os.listdir(source_dir)
print(<span class="hljs-string">"Files in source directory: "</span> + source_dir)
files_html = [i <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> files <span class="hljs-keyword">if</span> i.endswith(<span class="hljs-string">".html"</span>)]
os.makedirs(os.path.dirname(target_dir), exist_ok=<span class="hljs-literal">True</span>)
<span class="hljs-keyword">for</span> file <span class="hljs-keyword">in</span> files_html:
    print(<span class="hljs-string">"Moving: "</span> + os.path.join(source_dir, file))
    print(<span class="hljs-string">"To: "</span> + os.path.join(source_dir, file))
    shutil.copy(os.path.join(source_dir, file), target_dir)

<span class="hljs-comment"># moving css, images, js folders</span>
target_dir = <span class="hljs-string">"../static_files/landingpage_option1/"</span>
source_dir = <span class="hljs-string">"../webflow-templates/landingpage_option1"</span>
sub_dirs = [<span class="hljs-string">"css"</span>, <span class="hljs-string">"images"</span>, <span class="hljs-string">"js"</span>]
list_dirs = os.listdir(source_dir)
<span class="hljs-keyword">for</span> sub_dir <span class="hljs-keyword">in</span> list_dirs:
    <span class="hljs-keyword">if</span> sub_dir <span class="hljs-keyword">in</span> sub_dirs:
        dir_to_copy = os.path.join(source_dir, sub_dir)
        target_sub_dir = os.path.dirname(target_dir + sub_dir + <span class="hljs-string">"/"</span>)
        shutil.copytree(dir_to_copy, target_sub_dir)
</code></pre>
<p>10. This will take the files from <code>webflow-templates/landingepage_option1</code> and move the <code>index.html</code> into your Django templates folder (make sure this is created if not already) <code>templates/landingepage_option1</code> folder inside Django</p>
<p>11. It then also moves all sub folders (css, js, images) into your <code>static_files</code> folder (or where ever you house your static files) into a folder called <code>static_files/landingpage_option1</code></p>
<p>12. Run <code>find_and_</code><a target="_blank" href="http://replace.py"><code>replace.py</code></a></p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/env python3</span>

<span class="hljs-comment"># add django static tags to index.html file</span>
<span class="hljs-keyword">from</span> bs4 <span class="hljs-keyword">import</span> BeautifulSoup <span class="hljs-keyword">as</span> bs

<span class="hljs-comment"># append load static</span>
<span class="hljs-keyword">with</span> open(<span class="hljs-string">"../templates/landingpage_option1/index.html"</span>, <span class="hljs-string">"r+"</span>) <span class="hljs-keyword">as</span> file:
    html = file.read()
    file.seek(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>)
    file.write(<span class="hljs-string">"{% load static %}\n"</span> + html)
<span class="hljs-comment"># replace all href links</span>
<span class="hljs-keyword">with</span> open(<span class="hljs-string">"../templates/landingpage_option1/index.html"</span>) <span class="hljs-keyword">as</span> in_file:
    html = in_file.read()
    soup = bs(html, <span class="hljs-string">"html.parser"</span>)
    <span class="hljs-keyword">for</span> link <span class="hljs-keyword">in</span> soup.findAll(<span class="hljs-string">"link"</span>):
        beg = <span class="hljs-string">"{% static 'landingpage_option3/"</span>
        end = <span class="hljs-string">"' %}"</span>
        link[<span class="hljs-string">"href"</span>] = beg + link[<span class="hljs-string">"href"</span>] + end
        <span class="hljs-comment"># {% static 'css/normalize.css' %}</span>
<span class="hljs-keyword">with</span> open(<span class="hljs-string">"../templates/landingpage_option1/index.html"</span>, <span class="hljs-string">"w"</span>) <span class="hljs-keyword">as</span> out_file:
    out_file.write(str(soup))

<span class="hljs-comment"># # replace all src in img tags</span>
<span class="hljs-keyword">with</span> open(<span class="hljs-string">"../templates/landingpage_option1/index.html"</span>) <span class="hljs-keyword">as</span> in_file:
    html = in_file.read()
    soup = bs(html, <span class="hljs-string">"html.parser"</span>)
    <span class="hljs-keyword">for</span> img <span class="hljs-keyword">in</span> soup.findAll(<span class="hljs-string">"img"</span>):
        beg = <span class="hljs-string">"{% static 'landingpage_option3/"</span>
        end = <span class="hljs-string">"' %}"</span>
        img[<span class="hljs-string">"src"</span>] = beg + img[<span class="hljs-string">"src"</span>] + end
        <span class="hljs-comment"># {% static 'css/normalize.css' %}</span>
<span class="hljs-keyword">with</span> open(<span class="hljs-string">"../templates/landingpage_option1/index.html"</span>, <span class="hljs-string">"w"</span>) <span class="hljs-keyword">as</span> out_file:
    out_file.write(str(soup))

<span class="hljs-comment"># # replace all src in script tags from webflow</span>
<span class="hljs-keyword">with</span> open(<span class="hljs-string">"../templates/landingpage_option1/index.html"</span>) <span class="hljs-keyword">as</span> in_file:
    html = in_file.read()
    soup = bs(html, <span class="hljs-string">"html.parser"</span>)
    script = soup.find(<span class="hljs-string">"script"</span>, src=<span class="hljs-string">"js/webflow.js"</span>)
    beg = <span class="hljs-string">"{% static 'landingpage_option1/"</span>
    end = <span class="hljs-string">"' %}"</span>
    script[<span class="hljs-string">"src"</span>] = beg + script[<span class="hljs-string">"src"</span>] + end

<span class="hljs-keyword">with</span> open(<span class="hljs-string">"../templates/landingpage_option1/index.html"</span>, <span class="hljs-string">"w"</span>) <span class="hljs-keyword">as</span> out_file:
    out_file.write(str(soup))
</code></pre>
<p>13. This script will replace all your referenced static folders and files with the Django template static tags. This will add {% load static %} to the top of your index.html file in the templates folder as well as add static tags to all your &lt;link href ’s as well as your &lt;script src=”{% static ‘landingpage_option1/js/webflow.js’ %}” type=”text/javascript”&gt;&lt;/script&gt;</p>
<p>14. You should be ready to go to see your new template rendering if you have a view in Django pointing to the template. Something like this <a target="_blank" href="http://views.py">views.py</a></p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.shortcuts <span class="hljs-keyword">import</span> render

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span>(<span class="hljs-params">request</span>):</span>
 <span class="hljs-keyword">return</span> render(request, <span class="hljs-string">"landingpage_option1/index.html"</span>, context)
</code></pre>
]]></content:encoded></item><item><title><![CDATA[How to make a request to Notion API using Python]]></title><description><![CDATA[Python script using the Notion API to read a specific page
Below is a Python script that uses the requests library to interact with the Notion API and read a specific page. This script automates the process described earlier.
Prerequisites:

Notion A...]]></description><link>https://rcmisk.com/how-to-make-a-request-to-notion-api-using-python</link><guid isPermaLink="true">https://rcmisk.com/how-to-make-a-request-to-notion-api-using-python</guid><category><![CDATA[Python]]></category><category><![CDATA[notion]]></category><category><![CDATA[chatgpt]]></category><category><![CDATA[automation]]></category><category><![CDATA[#howtos]]></category><dc:creator><![CDATA[rcmisk]]></dc:creator><pubDate>Thu, 23 May 2024 19:38:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/JkZg8c61BoU/upload/58795caeb1d54e8d3b9a0fb31a399194.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-python-script-using-the-notion-api-to-read-a-specific-page">Python script using the Notion API to read a specific page</h1>
<p>Below is a Python script that uses the <code>requests</code> library to interact with the Notion API and read a specific page. This script automates the process described earlier.</p>
<h3 id="heading-prerequisites">Prerequisites:</h3>
<ol>
<li><strong>Notion Account</strong>: Ensure you have a Notion account and a page you want to read.</li>
<li><strong>API Token</strong>: Obtain an integration token from Notion by creating an integration at <a target="_blank" href="https://www.notion.so/my-integrations">Notion Integrations</a>.<ol>
<li>Copy your<strong><code>Internal Integration Secret</code> and use that as your <code>token</code></strong></li>
</ol>
</li>
<li><p>Install the <code>requests</code> library if you haven't already:</p>
<pre><code class="lang-bash"> pip install requests
</code></pre>
</li>
</ol>
<h3 id="heading-python-script">Python Script:</h3>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> requests

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_notion_page</span>(<span class="hljs-params">page_id, token</span>):</span>
    url = <span class="hljs-string">f"&lt;https://api.notion.com/v1/pages/<span class="hljs-subst">{page_id}</span>&gt;"</span>

    headers = {
        <span class="hljs-string">"Authorization"</span>: <span class="hljs-string">f"Bearer <span class="hljs-subst">{token}</span>"</span>,
        <span class="hljs-string">"Notion-Version"</span>: <span class="hljs-string">"2022-06-28"</span>,
        <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>
    }

    response = requests.get(url, headers=headers)

    <span class="hljs-keyword">if</span> response.status_code == <span class="hljs-number">200</span>:
        print(<span class="hljs-string">"Page details:"</span>)
        print(response.json())
    <span class="hljs-keyword">else</span>:
        print(<span class="hljs-string">f"Failed to retrieve page. Status code: <span class="hljs-subst">{response.status_code}</span>"</span>)
        print(response.text)

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    <span class="hljs-comment"># Replace these variables with your actual values</span>
    page_id = <span class="hljs-string">"your_page_id"</span>
    token = <span class="hljs-string">"your_integration_token"</span>

    get_notion_page(page_id, token)
</code></pre>
<h3 id="heading-how-to-use">How to Use:</h3>
<ol>
<li>Replace <code>"your_page_id"</code> with the actual ID of the Notion page you want to read.</li>
<li>Replace <code>"your_integration_token"</code> with your actual Notion integration token.</li>
<li>Run the script.</li>
</ol>
<h3 id="heading-running-the-script">Running the Script:</h3>
<p>Save the script to a file, for example <code>notion_read_page.py</code>, and run it using Python:</p>
<pre><code class="lang-bash">python notion_read_page.py
</code></pre>
<p>The script will send a GET request to the Notion API and print the details of the specified page.</p>
<p>Written with the help of Gen AI!</p>
]]></content:encoded></item><item><title><![CDATA[Launching IdeaVerify to Streamline Idea Validation - My #BuildInPublic Journey]]></title><description><![CDATA[Introduction
I'm finally going to start my #buildinpublic journey! On the journey you will see how I came across a problem, how I chose to solve it and finally how to launch! Each week I'll post what I've worked on and share what I built, so others c...]]></description><link>https://rcmisk.com/launching-ideaverify-to-streamline-idea-validation-my-buildinpublic-journey</link><guid isPermaLink="true">https://rcmisk.com/launching-ideaverify-to-streamline-idea-validation-my-buildinpublic-journey</guid><category><![CDATA[Build In Public]]></category><category><![CDATA[django rest framework]]></category><category><![CDATA[Django]]></category><category><![CDATA[React]]></category><category><![CDATA[SaaS]]></category><category><![CDATA[Indie Maker]]></category><category><![CDATA[indiedev]]></category><category><![CDATA[Learning Journey]]></category><category><![CDATA[learn coding]]></category><category><![CDATA[Learn Code Online]]></category><category><![CDATA[learning]]></category><dc:creator><![CDATA[rcmisk]]></dc:creator><pubDate>Tue, 19 Mar 2024 03:24:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/xX2aYSBsyKo/upload/ac70c404e8bf33765d8c619a7258b0cb.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-introduction">Introduction</h3>
<p>I'm finally going to start my #buildinpublic journey! On the journey you will see how I came across a problem, how I chose to solve it and finally how to launch! Each week I'll post what I've worked on and share what I built, so others can learn along the way. Let's get started!</p>
<h3 id="heading-the-genesis">The Genesis:</h3>
<p>My coding adventure began as an attempt to bring my brother's vision for a college-centric social network to life. Despite months of development and launching with an initial user base, the lack of market validation led to its stagnation. This pivotal experience highlighted the crucial step we missed—validating the idea before fully committing to its development.</p>
<h3 id="heading-the-awakening">The Awakening:</h3>
<p>Fast forward to the present, I've recognized a recurring pattern in my approach to projects—a cycle of enthusiasm followed by a realization of misalignment with market needs. This realization sparked the concept of 'IdeaVerify', a meta-solution born from the lessons learned throughout my journey.</p>
<p>Below is the manual process I did to test out this idea:</p>
<ol>
<li><p>Came up with a solution to a problem.</p>
</li>
<li><p>I built a simple landing page describing the problem and the solution.</p>
</li>
<li><p>I described the features, services, and prices with CTA's like Signup Now, or Subscribe now etc. All of the Purchase Now CTA's would not actually allow the user to purchase the SaaS but would show the user to sign up for a waitlist.</p>
</li>
<li><p>I then took that static page and deployed it on places for free like <a target="_blank" href="https://www.netlify.com">Netlify</a> or <a target="_blank" href="https://www.render.com">Render</a></p>
</li>
<li><p>The only thing I had to pay for to make it look legit was a Domain address, and to do that I would go to <a target="_blank" href="https://www.namecheap.com">Namecheap</a> to purchase a cheap domain.</p>
</li>
<li><p>Once created I published it to channels like Reddit, Twitter, Quora, Linkedin, Facebook and showed it to users of whom I thought would benefit.</p>
</li>
<li><p>I then analyzed what type of interest I got and from there I made a decision if it's worth it to build.</p>
</li>
</ol>
<p>This is how I came up with Ideaverify! I actually went through the entire <em>manual</em> process above and got a lot of interest by just posting on one or two subreddits on Reddit. I got signups, and messages saying that it was real interesting and that people were wondering when it would be ready.</p>
<p>This is how Ideaverify was born. I wanted to automate this entire process of validating or verifying an idea. It would automate all of the above, all at the click of a button but also provide analytics and a "go or no go" decision.</p>
<p>In this #buildinpublic series I will be using my <a target="_blank" href="https://boilerplate.rcmisk.com">boilerplate.rcmisk.com</a> SaaS starter kit built in Django and React with all the common SaaS features: transactional emails w/ Postmark, Stripe for payments/subscriptions, Google Analytics, dynamic landing page and copy creation, blog, user login/registration flows, Material UI for styling and deployment to Render.</p>
<p>I'm going to break up this project into different parts, and milestones and show the code, learnings and development along the way!</p>
<h3 id="heading-milestone-1">Milestone 1</h3>
<ol>
<li><p>Allow user to input what their SaaS is all about and then generate a landing page based off the copy the user provided and different template styles</p>
</li>
<li><p>Automatically create a google analytics app pointing to the url for tracking</p>
</li>
<li><p>User clicks "Create" and behind the scenes deploy the static landing page pointing to a unique URL associated with ideaverify.com subdomain. i.e. my-new-idea.ideaverify.com</p>
</li>
<li><p>Allow user to update copy, images, and style once it's deployed</p>
</li>
</ol>
<h3 id="heading-milestone-2">Milestone 2</h3>
<ol>
<li><p>Allow user to pick niche subreddits on Reddit and post to from within the ideaverify dashboard.</p>
</li>
<li><p>Show top subreddits/posts on Reddit to post to based off key words that their SaaS is associated with.</p>
</li>
</ol>
<h3 id="heading-milestone-3">Milestone 3</h3>
<ol>
<li>Allow user to point it to their custom domain</li>
</ol>
<p>This I believe will be a good MVP and stopping place to launch Ideaverify!</p>
<p>Follow me here:</p>
<p>https://rcmisk.com</p>
<p><a target="_blank" href="https://twitter.com/rcmisk">https://twitter.com/rcmisk</a></p>
<p><a target="_blank" href="https://www.indiehackers.com/rcmisk">https://indiehackers.com/rcmisk</a></p>
<p><a target="_blank" href="https://dev.to/rcmisk">https://dev.to/rcmisk</a></p>
<p>Sign up for my <a target="_blank" href="https://rcmisk.com/newsletter">newsletter</a> to keep track of my progress and to learn along the way!</p>
<p><a class="user-mention" href="https://hashnode.com/@rcmisk">rcmisk</a></p>
]]></content:encoded></item><item><title><![CDATA[How to Set Up a Django and React SaaS Boilerplate: A Step-by-Step Guide]]></title><description><![CDATA[Starting a Software as a Service (SaaS) project can be daunting, especially with the initial setup of the infrastructure. Luckily, with the right boilerplate, much of the groundwork is laid out for you. This guide will walk you through setting up a S...]]></description><link>https://rcmisk.com/how-to-set-up-a-django-and-react-saas-boilerplate-a-step-by-step-guide</link><guid isPermaLink="true">https://rcmisk.com/how-to-set-up-a-django-and-react-saas-boilerplate-a-step-by-step-guide</guid><category><![CDATA[Django]]></category><category><![CDATA[django rest framework]]></category><category><![CDATA[DjangoRestFramework]]></category><category><![CDATA[React]]></category><category><![CDATA[React]]></category><category><![CDATA[material ui]]></category><category><![CDATA[stripe]]></category><category><![CDATA[buildinpublic]]></category><category><![CDATA[buildingandlearning]]></category><category><![CDATA[indiehackers]]></category><dc:creator><![CDATA[rcmisk]]></dc:creator><pubDate>Tue, 12 Mar 2024 01:37:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/vpOeXr5wmR4/upload/59adc0bf7525b890d1a32843055db1c5.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709722694656/57f37422-d427-44c0-8261-d67f7cfdb813.png" alt class="image--center mx-auto" /></p>
<p>Starting a Software as a Service (SaaS) project can be daunting, especially with the initial setup of the infrastructure. Luckily, with the right boilerplate, much of the groundwork is laid out for you. This guide will walk you through setting up a SaaS application using a specific Django backend and React frontend code, including integrating Stripe for payments, Postmark for emails, user management and authentication, Cloudinary for media file uploads, dynamically rendered landing page, blog creation through Django Admin, Google Analytics, Material UI, setting up a PostgreSQL database, and deploying to <a target="_blank" href="http://Render.com">Render.com</a>. Let's dive in!</p>
<h3 id="heading-backend-setup"><strong>Backend Setup</strong></h3>
<h4 id="heading-initial-setup">Initial Setup</h4>
<ol>
<li><p><strong>Create a Project Folder</strong>: Start by navigating to your Desktop or preferred directory in your terminal and create a folder named <code>saas-boilerplate</code>. Then, create a subdirectory named <code>backend</code> and navigate into it.</p>
<pre><code class="lang-plaintext"> cd Desktop
 mkdir saas-boilerplate
 cd saas-boilerplate
 mkdir backend
 cd backend
</code></pre>
</li>
<li><p><strong>Clone the Boilerplate Repository</strong>: Clone the backend boilerplate repository into your <code>backend</code> directory.</p>
<pre><code class="lang-plaintext"> git clone https://github.com/rcmiskin10/dj_react_saas_backend_render_template.git .
</code></pre>
</li>
<li><p><strong>Set Up a Virtual Environment</strong>: Use <code>virtualenv</code> to create an isolated Python environment. Activate it and install the project's dependencies.</p>
<pre><code class="lang-plaintext"> pip install virtualenv
 python3.8 -m venv env
 source env/bin/activate
 pip install -r requirements.txt
</code></pre>
</li>
<li><p><strong>Environmental Variables</strong>: Create a <code>.env</code> file in your <code>backend</code> directory. Populate it with the necessary environmental variables provided below:</p>
<ol>
<li><pre><code class="lang-plaintext">  STRIPE_API_TEST_PK=&lt;STRIPE_API_TEST_PK&gt;
  STRIPE_API_TEST_SK=&lt;STRIPE_API_TEST_SK&gt;
  STRIPE_LIVE_MODE=False
  PROD_BACKEND_URL=&lt;PROD_BACKEND_URL&gt;
  PROD_FRONTEND_URL=&lt;PROD_FRONTEND_URL&gt;
  BACKEND_URL=http://127.0.0.1
  FRONTEND_URL=http://localhost:3000
  DEBUG=True
  DEV_EMAIL_HOST_USER=&lt;DEV_EMAIL_HOST_USER&gt;
  DEV_EMAIL_HOST_PASSWORD=&lt;DEV_EMAIL_HOST_PASSWORD&gt;
  POSTMARK_SERVER_TOKEN=&lt;POSTMARK_SERVER_TOKEN&gt;
  DEFAULT_FROM_EMAIL=&lt;DEFAULT_FROM_EMAIL&gt;
</code></pre>
</li>
</ol>
</li>
<li><p><strong>Now you need to set up Stripe.</strong></p>
<ol>
<li><p>If you do not have a stripe account register a free one here: https://dashboard.stripe.com/register. If you do have a stripe account login here: https://dashboard.stripe.com/login.</p>
</li>
<li><p>Once account is set up or you're logged in, turn on `Test Mode` (Toggle button in top right corner of dashboard) it should bring you to: https://dashboard.stripe.com/test/payments</p>
</li>
<li><p>Go to https://dashboard.stripe.com/test/apikeys</p>
<ol>
<li><p>Grab your `Publishable key` token and set the environmental variable `STRIPE_API_TEST_PK` in your `.env` file created above.</p>
</li>
<li><p>Next grab your `Secret key` token and set the environmental variable `STRIPE_API_TEST_SK` in your `.env` file created from above.</p>
</li>
<li><p>Next go to <a target="_blank" href="https://dashboard.stripe.com/test/products?active=true">Stripe Products</a> to add your products, i.e. subscription tiers</p>
<ol>
<li><p>Add as many tiers as you want i.e.</p>
<ol>
<li><p>Free w/ a description and price: $0</p>
</li>
<li><p>Pro w/ a description and price: say $10</p>
</li>
<li><p>Make sure both are Recurring and Monthly or however you would like to set up your subscriptions.</p>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
<li><p><strong>Postgres Database Setup</strong>: Download and install PostgreSQL. You can use this download <a target="_blank" href="https://postgresapp.com/">link</a>. Once installed, create a database named <code>postgres</code> and ensure your <a target="_blank" href="http://settings.py"><code>settings.py</code></a> reflects the correct database settings. For example: <code>default="postgresql://postgres:postgres@localhost/postgres"</code>, if your database name is <code>postgres</code>.</p>
</li>
<li><p><strong>Apply migrations and create a superuser for Django admin access.</strong></p>
<pre><code class="lang-plaintext"> python manage.py migrate
 python manage.py createsuperuser
</code></pre>
</li>
<li><p><strong>Running the Server</strong>: Start your Django server and log into the admin panel to verify that everything is set up correctly.</p>
<pre><code class="lang-plaintext"> python manage.py runserver
</code></pre>
</li>
</ol>
<h4 id="heading-integrating-stripe-products-and-prices-email-and-landing-page-data">Integrating Stripe Products and Prices, Email, and Landing Page data</h4>
<ol>
<li><p><strong>Add Products and Prices</strong>: Navigate to the Django admin payments <a target="_blank" href="http://127.0.0.1:8000/admin/payments/product/">section</a> to add Stripe products and their prices. Ensure each product in Django matches its counterpart in Stripe, including the recurring intervals and price IDs.</p>
<ol>
<li><p>You can find Stripe Product Id in <a target="_blank" href="https://dashboard.stripe.com/test/products?active=true">stripe products</a> and click on product and in the URL you will see an ID with prefix <code>prod_xxxxxxxx</code> and letters and numbers as the unique id.</p>
</li>
<li><p>Do the same for all products</p>
</li>
<li><p>Now go to <a target="_blank" href="http://127.0.0.1:8000/admin/payments/productprice/">Django Admin Product Price</a></p>
<ol>
<li><p>Add the price for each product from Stripe here: <a target="_blank" href="http://127.0.0.1:8000/admin/payments/productprice/add/">Django Admin Add Product Link</a></p>
</li>
<li><p>Select <code>Product</code> that you added to Django Admin.</p>
</li>
<li><p>Add the Price from what you entered on Stripe. Add the interval, i.e. Monthly</p>
</li>
<li><p>Add the stripe price id</p>
<ol>
<li><p>Go to <a target="_blank" href="https://dashboard.stripe.com/test/products?active=true">https://dashboard.stripe.com/test/products?active=true</a></p>
<ol>
<li>Select the Product and scroll down to <code>Pricing</code> and look under <code>API ID</code> and copy id with prefix and numbers/letters like <code>price_xxxxxxxxx</code> and paste into stripe price id on django admin. 5. Do for all products</li>
</ol>
</li>
</ol>
</li>
<li><p>Now you can add descriptions of the tiers here: <a target="_blank" href="http://127.0.0.1:8000/admin/payments/productdescriptionitem/add/">Django Admin Add Product Description Link</a></p>
<ol>
<li>Add as many description list items for each product</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
<li><p><strong>Email Setup</strong>: Configure your development email settings by adding <code>DEV_EMAIL_HOST_USER</code> and <code>DEV_EMAIL_HOST_PASSWORD</code> to your <code>.env</code> file, using an app password generated from your Gmail account.</p>
<ol>
<li><p>Set <code>DEV_EMAIL_HOST_USER</code> to your <code>gmail</code> for DEV testing</p>
</li>
<li><p>Set your <code>DEV_EMAIL_HOST_PASSWORD</code> to the password set up in your gmail account from above. You need to create an App Password in: <a target="_blank" href="https://myaccount.google.com/security">https://myaccount.google.com/security</a></p>
<ol>
<li><p>Make sure 2-Step Authentication is enabled.</p>
</li>
<li><p>Then go to <a target="_blank" href="https://myaccount.google.com/apppasswords">https://myaccount.google.com/apppasswords</a> and create a new app and a new password will be created.</p>
</li>
<li><p>Now Emails will be sent through gmail smtp in DEV</p>
</li>
</ol>
</li>
</ol>
</li>
<li><p>Next up is setting up the landing page data.</p>
<ol>
<li><p>Note here you can add whatever copy, images, icons, you would like through the Django admin</p>
<ol>
<li><p>Go to <a target="_blank" href="http://127.0.0.1:8000/admin/landingpage/landingpage/add/">http://127.0.0.1:8000/admin/landingpage/landingpage/add/</a></p>
<ol>
<li><p>Add the hero section copy and image</p>
</li>
<li><p>Add the features of your SaaS</p>
<ol>
<li><p>the <code>Feature mui icon name:</code> can be selected from <a target="_blank" href="https://mui.com/material-ui/material-icons/?query">https://mui.com/material-ui/material-icons/?query</a> i.e. find <code>Add</code> and get the name of the icon from the end of the import in the modal <code>import AddIcon from '@mui/icons-material/Add';</code> i.e. <code>Add</code></p>
</li>
<li><p>You can add as many features as you like and <code>Order</code> them however you like by applying <code>1,2,3...</code> to each feature. i.e. 1 will be first in line.</p>
</li>
<li><p>Do the same for how it works, Secondary hero, and Social Media Links</p>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
<li><p>We will set up prod environmental variables later.</p>
</li>
</ol>
<h3 id="heading-frontend-setup"><strong>Frontend Setup</strong></h3>
<ol>
<li><p><strong>Setting Up the Frontend</strong>: In a new terminal, navigate to your project root and set up the frontend directory by cloning the frontend boilerplate repository. Install dependencies with <code>npm install</code>.</p>
<pre><code class="lang-plaintext"> mkdir frontend
 cd frontend
 git clone https://github.com/rcmiskin10/dj_react_saas_frontend_render_template.git .
 npm install
</code></pre>
</li>
<li><p><strong>Configure Environment Variables</strong>: Create a <code>.env</code> file in your <code>frontend</code> directory. Add your Stripe Publishable key and Google Analytics ID.</p>
<ol>
<li><p>Add the publishable key from Stripe to the environmental variable <code>REACT_APP_STRIPE_API_TEST_PK</code> with the value from <code>STRIPE_API_TEST_PK</code> in <code>backend/.env</code></p>
</li>
<li><p>Now add an environmental variable <code>REACT_APP_GA_ID</code> for Google Analytics</p>
<ol>
<li><p>Go to <a target="_blank" href="https://analytics.google.com/">https://analytics.google.com/</a></p>
<ol>
<li><p>Add an analytics account</p>
</li>
<li><p>Add an app</p>
</li>
<li><p>Go to top search bar and search for <code>MEASUREMENT ID</code> in <code>Data Streams</code> and copy the ID that has prefix <code>G-XXXXXXXX</code> 4. Past that <code>G-XXXXXXXX</code> as value for environmental variable <code>REACT_APP_GA_ID</code> in <code>frontend/.env</code></p>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
<li><p><strong>Start the Frontend Server</strong>: Run <code>npm start</code> to launch your React app. Your SaaS landing page should now be visible at <a target="_blank" href="http://localhost:3000/"><code>http://localhost:3000/</code></a>.</p>
</li>
</ol>
<h3 id="heading-deploying-torendercomhttprendercom"><strong>Deploying to</strong><a target="_blank" href="http://Render.com"><strong>Render.com</strong></a></h3>
<ol>
<li><p><strong>Prepare for Deployment</strong>: Ensure your backend and frontend folders are pushed to separate GitHub repositories.</p>
</li>
<li><p><strong>Deploying the Backend</strong>: Follow <a target="_blank" href="http://Render.com">Render.com</a>'s documentation to deploy your Django app, adding the necessary environment variables for production, including Stripe keys, Cloudinary for media files, and email settings via Postmark.</p>
<ol>
<li><p>Follow tutorial here: <a target="_blank" href="https://docs.render.com/deploy-django#use-renderyaml-for-deploys">https://docs.render.com/deploy-django#use-renderyaml-for-deploys</a></p>
<ol>
<li><p>The <code>backend/render.yaml</code> already exists in the saas boilerplate. You can change all the names to fit whatever you like, but our boilerplate named everything <code>backend</code></p>
<ol>
<li><p>In the Render Dashboard, go to the <a target="_blank" href="https://dashboard.render.com/blueprints">Blueprints</a> page and click New Blueprint Instance.</p>
<ol>
<li><p>Make sure to first create a repository to hold your <code>backend/</code> folder on github.</p>
</li>
<li><p>Now you can select the repository that contains your blueprint and click Connect.</p>
</li>
<li><p>Give your blueprint project a name and click Apply.</p>
</li>
</ol>
</li>
<li><p>Now add the Production Environmental Variables by clicking the <code>backend</code> app and going to <code>Environment</code></p>
<ol>
<li><p>Add you Stripe <code>STRIPE_API_TEST_PK</code> found in your local <code>.env</code> file or if you are ready to use live Stripe data use the Production PK found in stripe dashboard.</p>
</li>
<li><p>Same with the <code>STRIPE_API_TEST_SK</code></p>
</li>
<li><p><code>STRIPE_LIVE_MODE=False</code></p>
</li>
<li><p><code>BACKEND_URL</code> will be the url that your backend render app is pointing to: something similar to this <a target="_blank" href="http://backend-xxxxonrender.com"><code>backend-xxxxonrender.com</code></a></p>
</li>
<li><p>For your media files first set up Cloudinary account by going to the <a target="_blank" href="https://cloudinary.com/">cloudinary</a> website and create a free cloudinary account. After your account has been created, go to the dashboard page and copy the cloud name, api key and api secret.</p>
<ol>
<li><p>Add the cloud name to <code>CLOUDINARY_CLOUD_NAME</code></p>
</li>
<li><p>Add the api key to <code>CLOUDINARY_API_KEY</code></p>
</li>
<li><p>Add the api secret to <code>CLOUDINARY_API_SECRET</code></p>
</li>
</ol>
</li>
<li><p>For your emails create a postmark account here <a target="_blank" href="https://postmarkapp.com/">https://postmarkapp.com/</a>. Follow instructions here: <a target="_blank" href="https://postmarkapp.com/support/article/1008-what-are-the-account-and-server-api-tokens">https://postmarkapp.com/support/article/1008-what-are-the-account-and-server-api-tokens</a> to find your Server API Token and set it to <code>POSTMARK_SERVER_TOKEN</code></p>
<ol>
<li>Set <code>DEFAULT_FROM_EMAIL</code> to your email with postmark.</li>
</ol>
</li>
<li><p>After you deploy the frontend app below, come back to setting <code>FRONTEND_URL</code></p>
</li>
<li><p>Finally run <code>python</code><a target="_blank" href="http://manage.py"><code>manage.py</code></a><code>createsuperuser</code> in the Render shell in the settings of the backend app so that you can create an admin account.</p>
<ol>
<li>To see your backend admin page, go to <code>&lt;YOUR_APP_URL&gt;/admin</code> to login.</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
<li><p><strong>Deploying the Frontend</strong>: Deploy your React app on <a target="_blank" href="http://Render.com">Render.com</a>, setting environment variables for connecting to the backend API, Stripe, Cloudinary, and Google Analytics.</p>
<ol>
<li><p>Connect your github account here: <a target="_blank" href="https://dashboard.render.com/select-repo?type=static">https://dashboard.render.com/select-repo?type=static</a></p>
<ol>
<li><p>Follow the instructions to deploy</p>
<ol>
<li><p>Once deployed go to settings for frontend app</p>
<ol>
<li><p>In Redirect/Rewrite rules add:</p>
<ol>
<li><p><code>/*</code> as Source</p>
</li>
<li><p><code>/index.html</code> as Destination</p>
</li>
<li><p>And <code>Rewrite</code> as Action</p>
</li>
</ol>
</li>
<li><p>Now go to <code>Environment</code> and add:</p>
<ol>
<li><p><code>REACT_APP_STRIPE_API_TEST_PK</code> to same Stripe API Key that you set in backend</p>
</li>
<li><p>Add <code>REACT_APP_BACKEND_API_URL</code> and use the backend url from above after backend was deployed</p>
</li>
<li><p>Add your <code>REACT_APP_MEDIA_URL</code> to the Cloudinary url. Something like this <a target="_blank" href="https://res.cloudinary.com/xxxxxxx/image/upload/v1/"><code>https://res.cloudinary.com/xxxxxxx/image/upload/v1/</code></a> The <code>xxxxxxx</code> should be found in your cloudinary dashboard that you used in backend set up.</p>
</li>
<li><p>Now add <code>REACT_APP_GA_ID</code> from your .env <code>REACT_APP_GA_ID</code> in local file.</p>
</li>
<li><p>Finally take the Frontend url from the react frontend app and set it in the <code>backend app</code> on <a target="_blank" href="http://render.com">render.com</a>'s environmental variable <code>FRONTEND_URL</code> that you previously set <a target="_blank" href="http://up.Final">up.</a></p>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
<li><p><a target="_blank" href="http://up.Final"><strong>Final</strong></a><strong>Adjustments</strong>: After deploying both frontend and backend, ensure all environment variables are correctly set and pointing to the right services. Test your live application to ensure everything works seamlessly.</p>
</li>
</ol>
<p>Congratulations! You've successfully set up and deployed your SaaS boilerplate application. This setup provides a solid foundation for developing your SaaS product, with scalable options for payment processing, database management, and deployment strategies. Happy coding!</p>
<p>Coming soon!</p>
<ul>
<li><p>Deploy to GCP Cloud Run w/ React inside Django's static files!</p>
</li>
<li><p>Automate all steps in this blog post, all at the click of button!</p>
</li>
<li><p>Tutorials on the code for both backend and frontend repos!</p>
<ul>
<li><p><a target="_blank" href="https://github.com/rcmiskin10/dj_react_saas_backend_render_template">https://github.com/rcmiskin10/dj_react_saas_backend_render_template</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/rcmiskin10/dj_react_saas_frontend_render_template">https://github.com/rcmiskin10/dj_react_saas_frontend_render_template</a></p>
</li>
</ul>
</li>
</ul>
<p>P.S comment below on any issues!</p>
<p>Sign up for my newsletter to hear about my #buildinpublic journey!</p>
]]></content:encoded></item><item><title><![CDATA[Coming soon - How to create a SaaS boilerplate written in Django and React]]></title><description><![CDATA[Finally finished my SaaS boilerplate written in Django and React! Check it out here: https://boilerplate.rcmisk.com
I realized when I had an idea for a SaaS or business I kept creating the same basic features, login/register, user management, JWT tok...]]></description><link>https://rcmisk.com/coming-soon-how-to-create-a-saas-boilerplate-written-in-django-and-react</link><guid isPermaLink="true">https://rcmisk.com/coming-soon-how-to-create-a-saas-boilerplate-written-in-django-and-react</guid><category><![CDATA[Django]]></category><category><![CDATA[django rest framework]]></category><category><![CDATA[stripe]]></category><category><![CDATA[SaaS]]></category><category><![CDATA[saas development ]]></category><category><![CDATA[boilerplate]]></category><category><![CDATA[Build In Public]]></category><category><![CDATA[buildingandlearning]]></category><category><![CDATA[buildinpublic]]></category><category><![CDATA[indie-hacker]]></category><category><![CDATA[indiedev]]></category><category><![CDATA[Indie Maker]]></category><dc:creator><![CDATA[rcmisk]]></dc:creator><pubDate>Thu, 29 Feb 2024 04:10:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1709180217368/8f77645b-ebe3-41d0-865d-47c2dacbf87c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709180173041/a1fd9fb0-cec4-4e90-92aa-6613c517f553.png" alt class="image--center mx-auto" /></p>
<p>Finally finished my SaaS boilerplate written in Django and React! Check it out here: <a target="_blank" href="https://boilerplate.rcmisk.com/">https://boilerplate.rcmisk.com</a></p>
<p>I realized when I had an idea for a SaaS or business I kept creating the same basic features, login/register, user management, JWT tokens, authentication, landing pages, profiles, Stripe integration, email, styling, Material UI, React, Google Analytics etc.</p>
<p>I thought there must be a smarter way. So I took the time coding up all those features and bundling them up into a repo so I can simply <code>git clone</code> and start immediately coding up my idea. No more wasted effort on re-creating the wheel each time. The repo is super generic so all I have to do is change the copy, colors, icons, and name of idea!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1709180185368/be642cb7-24f1-4d3d-96ce-561664e963b4.png" alt class="image--center mx-auto" /></p>
<p>I'll be starting up some blog post series on how I went about coding this up in Django and React and all the integrations that I set it up with.</p>
<p>Feel free to leave feedback!</p>
<p>Posts coming soon! Excited to share! #buildinpublic.</p>
]]></content:encoded></item><item><title><![CDATA[Authenticate user via Facebook and generate a JWT token via Django Rest Framework]]></title><description><![CDATA[I’m trying to build a server side application that will authenticate users via Facebook for what ever client side apps I want to develop.
I was hoping to be able to simply authenticate users via Facebook from either an iOS/Android React Native app an...]]></description><link>https://rcmisk.com/authenticate-user-via-facebook-and-generate-a-jwt-token-via-django-rest-framework</link><guid isPermaLink="true">https://rcmisk.com/authenticate-user-via-facebook-and-generate-a-jwt-token-via-django-rest-framework</guid><category><![CDATA[Django]]></category><category><![CDATA[django rest framework]]></category><category><![CDATA[Facebook]]></category><category><![CDATA[JWT]]></category><category><![CDATA[JWT token,JSON Web,Token,Token authentication,Access token,JSON token,JWT security,JWT authentication,Token-based authentication,JWT decoding,JWT implementation]]></category><category><![CDATA[React Native]]></category><category><![CDATA[Python]]></category><category><![CDATA[DjangoRestFramework]]></category><category><![CDATA[React]]></category><dc:creator><![CDATA[rcmisk]]></dc:creator><pubDate>Sat, 24 Feb 2024 23:09:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/iurEAyYyU_c/upload/ebe20c38c6c46fe762c38afe8470e145.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I’m trying to build a server side application that will authenticate users via Facebook for what ever client side apps I want to develop.</p>
<p>I was hoping to be able to simply authenticate users via Facebook from either an iOS/Android React Native app and/or React web app without having to use a ton of other python libraries.</p>
<p>Here are some helpful tutorials/links that helped me achieve this. Thank you!:</p>
<h2 id="heading-implementing-google-login-with-jwt-in-django-for-restful-api-authenticationhttpsmediumcomgerrysabarimplementing-google-login-with-jwt-in-django-for-restful-api-authentication-eaa92e50522dsourcepostpage-c36fb84a3d9c"><a target="_blank" href="https://medium.com/@gerrysabar/implementing-google-login-with-jwt-in-django-for-restful-api-authentication-eaa92e50522d?source=post_page-----c36fb84a3d9c--------------------------------"><strong>Implementing Google Login With JWT in Django for RESTful API Authentication</strong></a></h2>
<h3 id="heading-its-become-common-now-implementing-restful-api-since-server-can-communicate-with-various-devices-to-communicate-eachhttpsmediumcomgerrysabarimplementing-google-login-with-jwt-in-django-for-restful-api-authentication-eaa92e50522dsourcepostpage-c36fb84a3d9c"><a target="_blank" href="https://medium.com/@gerrysabar/implementing-google-login-with-jwt-in-django-for-restful-api-authentication-eaa92e50522d?source=post_page-----c36fb84a3d9c--------------------------------">It’s become common now implementing RESTful API since server can communicate with various devices to communicate each…</a></h3>
<p><a target="_blank" href="https://medium.com/@gerrysabar/implementing-google-login-with-jwt-in-django-for-restful-api-authentication-eaa92e50522d?source=post_page-----c36fb84a3d9c--------------------------------">medium.com</a></p>
<h2 id="heading-manually-build-a-login-flow-facebook-login-documentation-facebook-for-developershttpsdevelopersfacebookcomdocsfacebook-loginmanually-build-a-login-flowsourcepostpage-c36fb84a3d9c"><a target="_blank" href="https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow?source=post_page-----c36fb84a3d9c--------------------------------"><strong>Manually Build a Login Flow - Facebook Login - Documentation - Facebook for Developers</strong></a></h2>
<h3 id="heading-implementing-google-login-with-jwt-in-django-for-restful-api-authenticationhttpsmediumcomgerrysabarimplementing-google-login-with-jwt-in-django-for-restful-api-authentication-eaa92e50522dsourcepostpage-c36fb84a3d9c-1"><a target="_blank" href="https://medium.com/@gerrysabar/implementing-google-login-with-jwt-in-django-for-restful-api-authentication-eaa92e50522d?source=post_page-----c36fb84a3d9c--------------------------------">Implementing Google Login With JWT in Django for RESTful API Authentication</a></h3>
<p><a target="_blank" href="https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow?source=post_page-----c36fb84a3d9c--------------------------------">developers.facebook.com</a></p>
<h1 id="heading-lets-first-set-up-the-django-app"><strong>Let’s first set up the Django app</strong></h1>
<pre><code class="lang-plaintext">cd projectsmkdir app-login-testcd app-login-testpython -m venv envsource env/bin/activatepip install django djangorestframework djangorestframework-simplejwt
</code></pre>
<p>Set the ‘rest_framework’ in settings.py under INSTALLED_APPS as well as add this for authenticating with JWT:</p>
<pre><code class="lang-plaintext">REST_FRAMEWORK = {    "DEFAULT_AUTHENTICATION_CLASSES": [        "rest_framework_simplejwt.authentication.JWTAuthentication",    ],}
</code></pre>
<p>Now let’s run server:</p>
<pre><code class="lang-plaintext">python manage.py makemigrationspython manage.py migratepython manage.py runserver
</code></pre>
<h1 id="heading-now-lets-set-up-our-developers-facebook-app"><strong>Now let’s set up our developer’s Facebook App</strong></h1>
<p>You can se that up here:</p>
<p><a target="_blank" href="https://developers.facebook.com/apps/">https://developers.facebook.com/apps/</a></p>
<ol>
<li><p>Create App -&gt; then create test app by hovering over top right corner</p>
</li>
<li><p>Record the AppID and App Secret from Settings -&gt; Basic</p>
</li>
<li><p>You shouldn’t have to add and URL’s when working on localhost/dev</p>
</li>
</ol>
<h1 id="heading-now-we-want-to-build-the-login-dialog-url"><strong>Now we want to build the login dialog url</strong></h1>
<p>This is the link the user will be taken to when the user clicks the Login In Via Facebook button on what ever sign ui screen you create:</p>
<p><img src="https://miro.medium.com/v2/resize:fit:852/1*YhKGsdi660mtkec6WnlJWA.png" alt /></p>
<pre><code class="lang-plaintext">https://www.facebook.com/v7.0/dialog/oauth?client_id={app-id}&amp;redirect_uri=http://localhost:8000/login&amp;scope=email--&gt; {app-id} is the app id you recorded from above from your facebook app.
--&gt; scope is the type of permission you want to have access to
--&gt; redirect uri is where you want this login link to be taken to after successful login... in this example we are going to redirect it to our api endpoint /login/Keep this url so that you can use it to login via the web browser
</code></pre>
<p>Scope is a comma or space separated list of Permissions to request from the person using your app.</p>
<h2 id="heading-reference-facebook-login-documentation-facebook-for-developershttpsdevelopersfacebookcomdocsfacebook-loginpermissionssourcepostpage-c36fb84a3d9c"><a target="_blank" href="https://developers.facebook.com/docs/facebook-login/permissions/?source=post_page-----c36fb84a3d9c--------------------------------"><strong>Reference - Facebook Login - Documentation - Facebook for Developers</strong></a></h2>
<h3 id="heading-each-permission-has-its-own-set-of-requirements-and-usage-that-are-subject-to-our-platform-policies-and-your-ownhttpsdevelopersfacebookcomdocsfacebook-loginpermissionssourcepostpage-c36fb84a3d9c"><a target="_blank" href="https://developers.facebook.com/docs/facebook-login/permissions/?source=post_page-----c36fb84a3d9c--------------------------------">Each permission has its own set of requirements and usage that are subject to our Platform Policies and your own…</a></h3>
<p><a target="_blank" href="https://developers.facebook.com/docs/facebook-login/permissions/?source=post_page-----c36fb84a3d9c--------------------------------">developers.facebook.com</a></p>
<p>Once successful login Facebook then creates a <code>code</code> which is a long random hash and it’s passed via the redirect uri with the param :</p>
<pre><code class="lang-plaintext">http://localhost:8000/login?code={code-generated-from-above-result}
</code></pre>
<h1 id="heading-now-back-into-django"><strong>Now back into Django</strong></h1>
<pre><code class="lang-plaintext"># create login app
python manage.py startapp login
</code></pre>
<p>In the login app create a views.py with the below:</p>
<p>Add the views into the urls.py in your main app in Django</p>
<p>#1 First go to login screen for facebook:</p>
<p>https://www.facebook.com/v7.0/dialog/oauth?client_id={FACEBOOK_APP_ID}&amp;redirect_uri=http://localhost:8000/login/&amp;state={"{st=state123abc,ds=123456789}"}&amp;scope=email</p>
<p>The above returns <code>code</code> param → then redirects to OUR /<code>login?code={code-generated-from-facebook}</code></p>
<h1 id="heading-walking-through-what-the-signinview-does"><strong>Walking through what the SignInView does</strong></h1>
<p>Once the app is redirected to the SignInView <code>/login?code={code-from-facebook-after-success-login}</code> we generate a payload with the required info in order to build the below url and try to grab the user’s access token. We take the code param from the #1 url above.</p>
<p>#2 <a target="_blank" href="https://graph.facebook.com/v7.0/oauth/access_token?client_id=1415988338584764&amp;redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Flogin%2F&amp;client_secret=%7Bapp_secret%7D&amp;code=%7Bcode-generated-from-above-result%7D">https://graph.facebook.com/v7.0/oauth/access_token?client_id=1415988338584764&amp;redirect_uri=http://localhost:8000/login/&amp;client_secret={app_secret}&amp;code={code-generated-from-above-result}</a></p>
<p>URL # 2 is what generates the access token and the response below:</p>
<pre><code class="lang-plaintext">{    “access_token”: “{users_access_token}”,    “token_type”: “bearer”,    “expires_in”: 5181851}
</code></pre>
<p>Store the <code>user_access_token</code> for use later on.</p>
<p>We then want to grab our (the developer’s access token in order to make requests to fetch user’s info as well as to inspect/debug the users_access_token to make sure it’s valid). Create payload which will then generate url #3 below.</p>
<p>#3 <a target="_blank" href="https://graph.facebook.com/oauth/access_token?client_id=%7Byour-app-id%7D&amp;client_secret=%7Byour-app-secret%7D&amp;grant_type=client_credentials">https://graph.facebook.com/oauth/access_token?client_id={your-app-id}&amp;client_secret={your-app-secret}&amp;grant_type=client_credentials</a></p>
<p>Here’ is a successful response from the above url:</p>
<pre><code class="lang-plaintext">{    “access_token”: “{developers-app-access-token}”,    “token_type”: “bearer”}
</code></pre>
<p>Now inspect the users access token → validate to make sure it’s still valid. Build payload so that we can build and hit the below url #4.</p>
<p>#4 <a target="_blank" href="https://graph.facebook.com/debug_token?input_token=%7Btoken-to-inspect%7D&amp;access_token=%7Bapp-token-or-admin-token%7D">https://graph.facebook.com/debug_token?input_token={token-to-inspect-or-users-access-token}&amp;access_token={app-token-or-admin-token-developer’s-access-token-from-above}</a></p>
<pre><code class="lang-plaintext">{“data”: {“app_id”: “appid1234567”,“type”: “USER”,“application”: “Example Test App”,“data_access_expires_at”: 1602126624,“expires_at”: 1599532509,“is_valid”: true,“issued_at”: 1594348509,“scopes”: [“public_profile”],“user_id”: “user-id123456”}}
</code></pre>
<p>Valid user_access_token ^^^^ and gives us access to the <code>user_id</code></p>
<p>Now we want to get user info more specifically the email address so that we can create or check if user exists. We can do that by building another payload and hitting the url #5 below. Fields are the user fields you want access to (the permissions you added in the scope way up top!).</p>
<p>#5 <a target="_blank" href="https://graph.facebook.com/%7Buser_id%7D?fields=id%2Cname%2Cemail&amp;access_token=%7Buser_access_token%7D">https://graph.facebook.com/{user_id}?fields=id,email&amp;access_token={user_access_token}</a></p>
<p>Then what the view does is checks if there is a user already in our DB with the email address. If not it will generate a new user with that email address and random password.</p>
<p>It then will generate a JWT token using <code>RefreshToken</code> .</p>
<p>And we can pass the response back.</p>
<pre><code class="lang-plaintext">token = RefreshToken.for_user(user) response = {}response["username"] = user.usernameresponse["access_token"] = str(token.access_token)response["refresh_token"] = str(token)
</code></pre>
<p>We now have successfully authenticated a user and generated a JWT token that can be returned to any type of client whether that be React-Native or React. Once the client receives that JWT token we want to store that on client side securely and safely. In React-Native i believe the best way to do that is in here:</p>
<h2 id="heading-securestorehttpsdocsexpoioversionslatestsdksecurestoresourcepostpage-c36fb84a3d9c"><a target="_blank" href="https://docs.expo.io/versions/latest/sdk/securestore/?source=post_page-----c36fb84a3d9c--------------------------------"><strong>SecureStore</strong></a></h2>
<h3 id="heading-expo-secure-store-provides-a-way-to-encrypt-and-securely-store-key-value-pairs-locally-on-the-device-each-expo-projecthttpsdocsexpoioversionslatestsdksecurestoresourcepostpage-c36fb84a3d9c"><a target="_blank" href="https://docs.expo.io/versions/latest/sdk/securestore/?source=post_page-----c36fb84a3d9c--------------------------------">expo-secure-store provides a way to encrypt and securely store key-value pairs locally on the device. Each Expo project…</a></h3>
<p><a target="_blank" href="https://docs.expo.io/versions/latest/sdk/securestore/?source=post_page-----c36fb84a3d9c--------------------------------">docs.expo.io</a></p>
<p>If you are using Expo.</p>
<p>You can also make your <code>/hello/</code>endpoint require authentication so that you can test if your JWT token is working!</p>
<p>#6 test auth for endpoint</p>
<pre><code class="lang-plaintext">curl -X GET http://localhost:8000/hello/ -H ‘Authorization: Bearer access_token_here’
</code></pre>
<p>This is just a test app to get Facebook oauth flow working. Thank you to</p>
<h2 id="heading-implementing-google-login-with-jwt-in-django-for-restful-api-authenticationhttpsmediumcomgerrysabarimplementing-google-login-with-jwt-in-django-for-restful-api-authentication-eaa92e50522dsourcepostpage-c36fb84a3d9c-2"><a target="_blank" href="https://medium.com/@gerrysabar/implementing-google-login-with-jwt-in-django-for-restful-api-authentication-eaa92e50522d?source=post_page-----c36fb84a3d9c--------------------------------"><strong>Implementing Google Login With JWT in Django for RESTful API Authentication</strong></a></h2>
<h3 id="heading-its-become-common-now-implementing-restful-api-since-server-can-communicate-with-various-devices-to-communicate-eachhttpsmediumcomgerrysabarimplementing-google-login-with-jwt-in-django-for-restful-api-authentication-eaa92e50522dsourcepostpage-c36fb84a3d9c-1"><a target="_blank" href="https://medium.com/@gerrysabar/implementing-google-login-with-jwt-in-django-for-restful-api-authentication-eaa92e50522d?source=post_page-----c36fb84a3d9c--------------------------------">It’s become common now implementing RESTful API since server can communicate with various devices to communicate each…</a></h3>
<p><a target="_blank" href="https://medium.com/@gerrysabar/implementing-google-login-with-jwt-in-django-for-restful-api-authentication-eaa92e50522d?source=post_page-----c36fb84a3d9c--------------------------------">medium.com</a></p>
<p>for helping me see how to do it for Google login!</p>
<p>Next I will be trying to set up React-Native and creating iOS and Android Login Screens that will contain this Facebook login button… and logging in and out users and allowing them to go to a private home screen that requires authentication to view! FYI → Postman was also very helpful in seeing what the facebook graph requests returned!</p>
<p><a target="_blank" href="https://medium.com/tag/django-rest-framework?source=post_page-----c36fb84a3d9c---------------django_rest_framework-----------------">Django Rest Framework</a></p>
<p><a target="_blank" href="https://medium.com/tag/facebook?source=post_page-----c36fb84a3d9c---------------facebook-----------------">Facebook</a></p>
<p><a target="_blank" href="https://medium.com/tag/authentication?source=post_page-----c36fb84a3d9c---------------authentication-----------------">  
</a></p>
]]></content:encoded></item></channel></rss>