ANTESDA AGULHA.

- ESTÚDIO
- MILWEB
- TIPO
- PRODUTO DIGITAL
- STATUS
- EM DESENVOLVIMENTO
02 / A IDEIA
SaaS de tatuagem · simulação de tattoo com IA
Cliente de tatuagem decide no escuro: só vê como a arte vai ficar na pele durante a sessão, já com a agulha na mão.
01 / 01
Segmentação de pele, estimativa de perspectiva e curvatura, warp do desenho e img2img de baixa intensidade pra harmonizar sombra e textura. Não é uma chamada única de IA.

02 / 02
packages/ai troca entre Fal.ai, Replicate, OpenAI, Gemini ou Stable Diffusion só com uma env var. Nenhum caso de uso importa um provider concreto.

03 / 03
Extensão do Prisma que exige studioId, Postgres RLS como defesa em profundidade e uma suíte automatizada de testes de isolamento cross-tenant.

04 / 04
Migration com tstzrange() só quebrava num migrate deploy de verdade (dev usa db push); achada no primeiro deploy Vercel+Neon, antes de chegar na VPS.

01
02
03
04
04 / POR BAIXO DO CAPÔ
01Monorepo pnpm + Turborepo com Clean Architecture explícita: apps/web (Next.js 15, App Router, React 19) cobre SSR/ISR das páginas públicas e…+
Monorepo pnpm + Turborepo com Clean Architecture explícita: apps/web (Next.js 15, App Router, React 19) cobre SSR/ISR das páginas públicas e Server Actions para mutações, apps/realtime isola o Socket.IO num processo dedicado (WebSocket de longa duração não convive bem com o modelo serverless do Next) e apps/worker roda os jobs assíncronos via BullMQ. A regra de negócio nunca mora nas rotas: fica em packages/core (use cases puros, sem I/O, DTOs validados com zod) que só conhece interfaces (ports) implementadas por packages/infra (repositórios Prisma). Essa decisão permitiria trocar a borda HTTP por outro framework sem tocar no domínio. Autenticação usa Better Auth em vez de Auth.js porque o plugin organization resolve membership multi-tenant nativamente (role ADMIN de plataforma e OWNER/MANAGER/ARTIST por estúdio; cliente é usuário sem membership).
02A simulação de tatuagem é a feature central e foi desenhada como um pipeline, não uma chamada única de IA: packages/ai expõe portas (TattooS…+
A simulação de tatuagem é a feature central e foi desenhada como um pipeline, não uma chamada única de IA: packages/ai expõe portas (TattooSimulationProvider, ImageGenerationProvider, SkinSegmentationProvider) e um registry que troca de provider (Fal.ai, Replicate, OpenAI, Gemini, Stable Diffusion) só com uma env var, sem nenhum caso de uso importando um provider concreto. O fluxo é segmentar a pele/parte do corpo, estimar perspectiva e curvatura, aplicar o desenho com warp na escala e posição escolhidas pelo cliente no editor (arrastar, redimensionar, rotacionar) e fechar com um passo de img2img de baixa intensidade que harmoniza sombra, textura e iluminação preservando o traço original. Como a geração leva de 10 a 60 segundos, a API só enfileira no BullMQ; o worker chama o provider e o resultado chega ao cliente por WebSocket (evento simulation:done), sem nenhum request HTTP pendurado esperando a IA responder. Cada chamada grava um AiUsageLog (provider, operação, custo) que alimenta o dashboard admin e os limites de crédito por plano.
03O isolamento multi-tenant é banco único com studioId, protegido em três camadas em vez de um filtro solto em cada query. Uma extensão do Pri…+
O isolamento multi-tenant é banco único com studioId, protegido em três camadas em vez de um filtro solto em cada query. Uma extensão do Prisma Client exige studioId em toda consulta de modelo tenant-scoped: lança erro em dev e bloqueia em prod. O RLS do Postgres (SET app.current_studio_id) segura mesmo se a camada de aplicação falhar. E uma suíte de testes tenta acesso cross-tenant em cada rota. Essa redundância pegou um bug real. O getActor() lia a tabela StudioMember, que é protegida por RLS, sem abrir o contexto de tenant nem de admin. A política nunca casava, a consulta voltava vazia mesmo com memberships reais, e o dono do estúdio acabava vendo um painel idêntico ao de um cliente comum. Em dev local o bug nunca apareceu, porque o role do Postgres usado ali ignora RLS por padrão. Só apareceu numa auditoria visual, comparando telas de verdade. O fix foi withAdmin(), seguro porque o filtro final continua sendo o próprio userId. A lição é direta: sem testar contra um role de produção, sem bypass de RLS, esse tipo de bug fica invisível por tempo indeterminado.
04O primeiro deploy real (Vercel + Neon, ambiente de teste antes da VPS de produção) revelou três bugs que nenhum ambiente anterior exercitava…+
O primeiro deploy real (Vercel + Neon, ambiente de teste antes da VPS de produção) revelou três bugs que nenhum ambiente anterior exercitava. A migration da constraint anti-overbooking usava tstzrange() num índice. Essa função depende do fuso da sessão (é STABLE, não IMMUTABLE) e quebra num migrate deploy de verdade. A correção foi trocar por tsrange(), que bate com o tipo real da coluna. O ambiente local usa prisma db push, que ignora SQL bruto, e o CI não rodava migrations, então o bug ficou invisível até o primeiro migrate deploy real. Teria travado o deploy na VPS também. O bundle serverless da Vercel também 'perdia' o query engine do Prisma num monorepo pnpm, resolvido com o plugin oficial de workaround, ativo só quando VERCEL=1 pra não afetar o Docker da VPS. E o rate limiting do Better Auth usava um limitador em memória, que simplesmente não funciona com múltiplas instâncias serverless. Trocado por armazenamento em banco: uma tabela RateLimit, deliberadamente fora do RLS, no mesmo padrão de User e Session, que também não são multi-tenant.
CONSTRUÍDO COM — Next.js · TypeScript · Prisma · PostgreSQL · Turborepo · StripeCÓDIGO ↗
06 / RESULTADO
CADA MILHASOB CONTROLE.
