Pitch: Meu Curso de Programação Para Quem Quer Se Sentir Competente 🎉
Turma, finalmente o meu curso chegou num estágio em que posso falar abertamente sobre ele e o primeiro lugar que escolhi fazer uma publicação oficial não poderia ser outro, a não ser aqui no TabNews 🤝
Então, a plataforma onde está o curso se chama curso.dev (o nome e o endereço são a mesma coisa) e o primeiro curso disponível é sobre Desenvolvimento Web 🎉 Na verdade, acaba sendo bem mais do que isso, pois é um download do meu cérebro sobre a parte técnica, negócio e carreira na programação, tudo para o aluno atingir de forma genuína a sensação de ser competente no que faz.
E para isso, estou reconstruindo do zero o TabNews lá dentro do curso e explicando como que eu apliquei todas as técnicas que eu uso para organizar e executar um projeto do início ao fim e sem desanimar ou desistir no meio.
Como a plataforma foi construída?
Eu estava louco para fazer essa publicação e dizer que a plataforma do curso.dev é um fork
do repositório do TabNews e isso me mostrou como que a arquitetura, modelagem e API aqui do TabNews está super flexível e consegue se adaptar para outras situações e iniciativas.
Ainda tem alguns detalhes que preciso ajustar do backend antes de disponibilizar o core
do curso.dev de forma Open Source (eu marretei algumas coisas de forma hard coded
e preciso tirar), mas a idéia é sim ter mais uma plataforma Open Source brasileira, agora voltada para cursos 💪
Então em cima do fork
original eu programei coisas específicas para uma plataforma de curso que eu vou expandir em tópicos logo abaixo, mas antes eu gostaria de começar com um Diagrama de Sequência revelando o fluxo principal entre o curso.dev e a Hotmart sobre o momento em que acontece uma matrícula:
0) Fluxo de compra
Não é um fluxo complicado, mas queria destacar que a Hotmart está sendo usada apenas para a parte de Pagamentos.
sequenceDiagram
actor Aluno
participant curso.dev
participant Hotmart
Aluno->>curso.dev: Acessa o site
curso.dev->>Hotmart: Faz o Pagamento
Hotmart-->>curso.dev: Webhook (Pagamento Aprovado)
curso.dev-->>Aluno: Envio do Código de Ativação por email
Aluno->>curso.dev: Faz o cadastro usando Código de Ativação
curso.dev-->>Aluno: Acesso às aulas
PS: este gráfico foi feito com o Mermeid
e tudo aqui por dentro do TabNews. Obrigado Felipe Barso por consertar o Preview
dele 🎉 Coloquei o Editor
no modo Full Screen
e dividi ele para mostrar o Texto
e Resultado
ao mesmo tempo para tudo ir atualizando em tempo-real... foi uma experiência sensacional 😍 E segue abaixo o código que foi usado para gerar o gráfico acima:
```mermaid
sequenceDiagram
actor Aluno
participant curso.dev
participant Hotmart
Aluno->>curso.dev: Acessa o site
curso.dev->>Hotmart: Faz o Pagamento
Hotmart-->>curso.dev: Webhook (Pagamento Aprovado)
curso.dev-->>Aluno: Envio do Código de Ativação por email
Aluno->>curso.dev: Faz o cadastro usando Código de Ativação
curso.dev-->>Aluno: Acesso às aulas
```
1) Integração com um Meio de Pagamento
Na parte do pagamento, por hora como falei, optei integrar com a Hotmart e estou usando os Webhooks
que eles fornecem e que avisam o backend
do curso.dev quando alguém faz uma Nova Matrícula para gerar um Código de Ativação ou, caso o aluno peça Reembolso, para automaticamente desabilitar o acesso à conta.
Esta imagem aqui de cima é de dentro do painel da Hotmart e mostra os logs dos Webhooks
. Se você clicar no ícone da lupa, é possível inspecionar tanto o que a Hotmart enviou na Request
, quanto o que a sua plataforma devolveu como Response
(incluindo o Status Code
que nos três exemplos acima foram 201
).
Esta integração está funcionando surpreendentemente bem e até o momento não gerou nenhum problema. Na verdade tudo indica que, até agora, só um caso a Hotmart não enviou um Webhook
de Estorno. E fora isso, teve um evento onde a API do curso.dev não estava respondendo (por um bloqueio rigoroso que fiz na Cloudflare) e a Hotmart fez com sucesso Retentativas do mesmo Webhook
. Tudo funcionou de forma transparente 🤝
O legal é que tudo está coberto com Testes de Integração que simulam a Hotmart fazendo um POST
contra o backend do curso.dev e isto dá uma baita segurança na hora de alterar o código e avançar com novas features.
2) Nova modelagem: Activation Codes
Uma característica da Matrícula, é que ao fazer ela você recebe por email um Código de Ativação que pode ser utilizado por você ou enviado para outra pessoa como um presente (e algumas pessoas já fizeram isso, muito legal).
A estrutura dela no Banco de Dados é simples:
CREATE TABLE activation_codes (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
active boolean NOT NULL DEFAULT true,
type text NOT NULL,
code text NOT NULL CHECK (length(code) = 23) UNIQUE,
available_redeems integer NOT NULL DEFAULT 1 CHECK (available_redeems >= 0),
redeemed_by jsonb DEFAULT '[]'::jsonb,
metadata jsonb,
created_at timestamp with time zone NOT NULL DEFAULT (now() AT TIME ZONE 'utc'::text),
updated_at timestamp with time zone NOT NULL DEFAULT (now() AT TIME ZONE 'utc'::text)
);
Em especial eu destaco a propriedade available_redeems
que controla quantas vezes um Código de Ativação pode ser usado (resgatado), que por padrão o valor é 1
e também a propriedade redeemed_by
que anota qual usuário (ou usuários) fizeram o resgate dele.
3) Nova modelagem: Lessons
Esta foi uma modelagem interessante de se fazer, pois ela é uma abstração em cima de uma modelagem que já existe no TabNews, que é a de Conteúdos. Todas as publicações no TabNews, sejam elas publicações que vão para a raiz do site (root
), sejam elas respostas a outras publicações (child
), tudo é um content
.
Da mesma forma, o conteúdo de uma aula dentro do curso.dev é também um content
e qualquer pessoa da plataforma consegue criar um conteúdo. Mas transformar esse conteúdo em uma aula/lição, fica sob a responsabilidade da lesson
, e por isso a tabela abaixo conta com um campo chamado content_id
. Este campo diz para a lesson
de onde vem o conteúdo dela:
CREATE TABLE lessons (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
owner_id uuid NOT NULL,
course_slug character varying NOT NULL,
slug character varying NOT NULL CHECK (length(slug::text) <= 256),
content_id uuid NOT NULL,
xp integer NOT NULL,
tags character varying[],
exit_options jsonb[],
listing_visibility character varying NOT NULL DEFAULT 'public'::character varying CHECK (listing_visibility::text = ANY (ARRAY['unlisted'::character varying, 'public'::character varying]::text[])),
listing_section character varying,
listing_order integer,
created_at timestamp with time zone NOT NULL DEFAULT (now() AT TIME ZONE 'utc'::text),
updated_at timestamp with time zone NOT NULL DEFAULT (now() AT TIME ZONE 'utc'::text),
CONSTRAINT lessons_uniqueness_fkey UNIQUE (course_slug, slug)
);
Outro detalhe é que toda lesson
possui um xp
, que são os Pontos de Experiência que a pessoa pode coletar ao final da aula, e estes pontos são somados ao xp
do usuário.
Isso significa que o curso.dev não possui o conceito de tabcoins
e tabcash
e eles foram alterados para coins
e xp
, respectivamente:
{
"id": "78a37e97-14bd-498e-a59d-e527a7cb30e0",
"username": "filipedeschamps",
"features": [
"course:web",
"create:session",
"read:session",
"create:content",
//...
],
"coins": 1321,
"xp": 2051,
"created_at": "2022-12-08T23:31:05.349Z",
"updated_at": "2022-12-14T20:44:30.223Z"
}
4) Nova modelagem: Lessons Progress
Esta modelagem controla o progresso do aluno pelas aulas e marca 3 estágios:
- Aluno não iniciou a aula (nada é registrado na tabela
lessons_progress
). - Aluno iniciou a aula, porém não marcou como concluída (é criado um novo registro na tabela
lessons_progress
, porém comcompleted_at = null
). - Aluno concluiu a aula (registro é atualizado com
completed_at = NOW()
).
Isso significa que, ao entrar numa aula, o frontend
já faz um POST
contra o backend
para sinalizar que você iniciou a aula e o endpoint
para isso é idempotente, ou seja, você pode fazer várias vezes um mesmo POST
, que sempre será retornado a mesma coisa (não será negado, caso você já tenha iniciado a aula).
E a tabela é bem simples:
CREATE TABLE lessons_progress (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
user_id uuid NOT NULL,
lesson_id uuid NOT NULL,
completed_at timestamp with time zone,
created_at timestamp with time zone NOT NULL DEFAULT (now() AT TIME ZONE 'utc'::text),
updated_at timestamp with time zone NOT NULL DEFAULT (now() AT TIME ZONE 'utc'::text),
CONSTRAINT lessons_progress_uniqueness_fkey UNIQUE (user_id, lesson_id)
);
E a junção da tabela lessons_progress
com lessons
, faz ser possível construir uma lista com as aulas e representar os 3 tipos de progresso visualmente:
- Ícone pontilhado: não iniciou a aula
- Ícone amarelo: iniciou a aula
- Ícone verde: concluiu a aula
5) Como estão organizadas as aulas?
Pela lista acima você pode ter notado que as aulas são organizadas de uma forma linear e separadas por Dias, onde cada Dia conta com uma aula curta e isto contribui muito com o progresso do Aluno por dentro dos materiais.
Fora isso, note que cada dia possui uma Pista Rápida e algumas Pistas Lentas. Eu explico melhor dentro do curso toda a estratégia por trás disso, mas em resumo é para conseguir comportar usuários iniciantes e avançados na mesma plataforma.
Então se você já tem maestria num determinado assunto, você pode rapidamente navegar pelas Pistas Rápidas, e caso precise ir com mais calma, você pode descer para a Pista Lenta e lá passamos um pente fino em tudo.
O curioso é que vários alunos que já se encontram num nível bem avançado na carreira reportaram estarem gostando muito das Pistas Lentas, pois através delas está sendo possível fechar todos os gaps de conhecimento que talvez ao longo da velocidade e truculência da vida real não foram possíveis de fechar. Eu não esperava que tantos alunos já com esta senioridade reportariam isso, mas foi algo muito muito muito legal de se ver, até porque com eles, a seção dos Comentários virou uma parte fundamental das aulas e todo mundo está se ajudando 🤝 💪
6) Aulas interativas
Este ponto eu não gostaria de revelar muito o que acontece, pois pode acabar sendo um baita spoiler de coisas interessantes que acontecem dentro das aulas... coisas que geram uma certa surpresa no aluno 😂
Bom, essa parte vou deixar apenas para quem se matriculou e vivenciou isso, mas meu objetivo foi criar uma "experiência viva" lá dentro, uma tentativa de realmente me "virtualizar" e estar o mais próximo possível do aluno, ao ponto de que se este mesmo aluno fizer um curso em outra plataforma, vai sentir que "algo está faltando" 🤝
Se você quiser uma pequena demonstração disso, convido a entrar na Home do curso.dev e assistir o vídeo de apresentação, pois possivelmente algo diferente do que você está acostumado vai acontecer 😍
Conclusão
Assim como eu fiz o Canal no YouTube para ter vídeos em português que eu gostaria de ver por lá, a Newsletter para ler por email notícias no formato que eu gostaria de ler, o TabNews para navegar na web e ler conteúdos de valor concreto sem ter medo de ser pego por algum dark pattern na Experiência do Usuário... eu criei o curso.dev para ser uma plataforma onde eu pudesse expressar e atingir o máximo que consigo da minha didática.
Bom, no mais, estou 100% aberto a AMA (Ask Me Anything), então qualquer dúvida que tiver sobre o curso, seja na parte técnica ou sobre as aulas, estou de plantão aqui para responder, independente de quando você ler esta publicação 🤝 💪