Carregar, enquadrar num circulo com zoom e arrasto, e guardar. A foto aparece na barra lateral, na lista de mensagens e no leitor, caindo para as iniciais quando nao ha nenhuma. A geometria vai em funcoes puras com testes, porque e onde os erros moram: a escala minima cobre o quadrado em vez de caber nele (caber deixa um canto vazio e o ficheiro exportado leva uma cunha transparente), e o deslocamento e preso a saliencia da imagem para que nenhum arrasto consiga trazer uma borda para dentro do circulo. Uma dimensao NaN -- o que uma imagem que nao descodificou reporta -- passava pelo guarda `<= 0`, porque NaN falha qualquer comparacao, e propagava-se ate apagar o editor. O recorte e exportado a partir dos pixeis da origem e nao da pre-visualizacao, por isso a foto guardada e tao nitida quanto o original permite em vez de ficar limitada ao tamanho do editor no ecra. Do lado do servidor o tipo e decidido pelos bytes, nunca pelo Content-Type nem pelo nome do ficheiro, que sao ambos escolhidos por quem carrega. SVG e recusado apesar de ser imagem: e um documento que pode levar script e seria servido de volta a partir da nossa propria origem. A leitura exige sessao, verificada na propria rota porque o guarda de prefixo so cobre /user/ e /admin/. Um endpoint aberto indexado por endereco responde "esta caixa existe" a quem quer que pergunte, para todos os enderecos que quiser experimentar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
18 lines
893 B
SQL
18 lines
893 B
SQL
-- The profile photo a mailbox shows inside LambdaMail.
|
|
--
|
|
-- Stored in the database rather than the blob spool: an avatar is small,
|
|
-- there is at most one per mailbox, and it is read on nearly every screen -
|
|
-- so it belongs where the mailbox row already is, and is deleted with it.
|
|
--
|
|
-- Not to be confused with BIMI, which is one logo for a whole domain and is
|
|
-- the only thing an external mail client ever renders.
|
|
CREATE TABLE IF NOT EXISTS mailbox_avatars (
|
|
mailbox_id UUID PRIMARY KEY REFERENCES mailboxes(id) ON DELETE CASCADE,
|
|
content_type VARCHAR(32) NOT NULL CHECK (content_type IN ('image/png','image/jpeg','image/webp')),
|
|
bytes BYTEA NOT NULL,
|
|
-- Lets a conditional request answer 304 instead of resending the image on
|
|
-- every screen that shows the person.
|
|
etag CHAR(64) NOT NULL,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|