revert: foto de perfil e BIMI, para restaurar o servico

O site ficou a responder 502 em todos os hostnames depois do deploy destas
duas funcionalidades. A reversao vem primeiro; o diagnostico vem a seguir, com
o servico de pe.

As migracoes 0012 e 0013 ficam. Sao aditivas, nada no codigo revertido as usa,
e apaga-las deixaria a base de dados numa versao sem ficheiro correspondente,
o que parte o proprio migrate no arranque seguinte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Opus 5 committed 2026-09-07 08:45:16 -03:00
1 parent 3e06162fa0
commit e30d000e43
26 files changed
+21 -1899

No files matched your search

-109
View File
@@ -1,109 +0,0 @@
import { describe, it, expect } from "vitest";
import {
minimumScale,
scaleRange,
clampTransform,
initialTransform,
sourceRect,
} from "./avatarCrop";
const VIEWPORT = 320;
describe("minimumScale", () => {
// Cover, not contain. Scaling a wide photo to fit its width leaves the top
// and bottom of the circle empty.
it("covers the frame on the short side of a wide image", () => {
expect(minimumScale({ width: 800, height: 400 }, VIEWPORT)).toBeCloseTo(320 / 400);
});
it("covers the frame on the short side of a tall image", () => {
expect(minimumScale({ width: 400, height: 800 }, VIEWPORT)).toBeCloseTo(320 / 400);
});
it("scales a small image up rather than leaving it small", () => {
expect(minimumScale({ width: 100, height: 100 }, VIEWPORT)).toBeCloseTo(3.2);
});
it.each([
[{ width: 0, height: 100 }],
[{ width: 100, height: 0 }],
[{ width: Number.NaN, height: 100 }],
])("survives a degenerate image %o", (image) => {
expect(Number.isFinite(minimumScale(image, VIEWPORT))).toBe(true);
});
});
describe("clampTransform", () => {
const image = { width: 800, height: 600 };
it("refuses to zoom below covering the frame", () => {
const t = clampTransform(image, VIEWPORT, { scale: 0.01, offsetX: 0, offsetY: 0 });
expect(t.scale).toBeCloseTo(minimumScale(image, VIEWPORT));
});
// Without this, dragging far enough pulls the edge of the photo into the
// circle and the export carries a transparent wedge.
it("stops the image at its own edge", () => {
const scale = minimumScale(image, VIEWPORT);
const t = clampTransform(image, VIEWPORT, { scale, offsetX: 99999, offsetY: 99999 });
const overhangX = (image.width * scale - VIEWPORT) / 2;
expect(t.offsetX).toBeCloseTo(overhangX);
// At the covering scale the short side has no slack at all, so it cannot
// move: any movement there would expose a gap.
expect(t.offsetY).toBeCloseTo(0);
});
it("allows movement on both axes once zoomed in", () => {
const scale = minimumScale(image, VIEWPORT) * 2;
const t = clampTransform(image, VIEWPORT, { scale, offsetX: 99999, offsetY: 99999 });
expect(t.offsetX).toBeGreaterThan(0);
expect(t.offsetY).toBeGreaterThan(0);
});
it("caps the zoom so the photo cannot be magnified into mush", () => {
const { max } = scaleRange(image, VIEWPORT);
expect(clampTransform(image, VIEWPORT, { scale: 9999, offsetX: 0, offsetY: 0 }).scale).toBeCloseTo(max);
});
});
describe("sourceRect", () => {
const image = { width: 800, height: 600 };
it("is always square, because the frame is", () => {
const r = sourceRect(image, VIEWPORT, { scale: 1.2, offsetX: 30, offsetY: -20 });
expect(r.width).toBeCloseTo(r.height);
});
// At the covering scale the whole short side is used: nothing of the height
// is cropped away, and the crop is the full 600px of it.
it("takes the whole short side at the covering scale", () => {
const r = sourceRect(image, VIEWPORT, initialTransform(image, VIEWPORT));
expect(r.height).toBeCloseTo(600);
expect(r.y).toBeCloseTo(0);
// Centred horizontally: (800 - 600) / 2.
expect(r.x).toBeCloseTo(100);
});
it("never reads outside the image", () => {
for (const wanted of [
{ scale: 0.001, offsetX: -9999, offsetY: -9999 },
{ scale: 9999, offsetX: 9999, offsetY: 9999 },
{ scale: 1, offsetX: 0, offsetY: 9999 },
]) {
const r = sourceRect(image, VIEWPORT, wanted);
expect(r.x).toBeGreaterThanOrEqual(-0.001);
expect(r.y).toBeGreaterThanOrEqual(-0.001);
expect(r.x + r.width).toBeLessThanOrEqual(image.width + 0.001);
expect(r.y + r.height).toBeLessThanOrEqual(image.height + 0.001);
}
});
// Dragging the image right shows what is further left in the source.
it("moves the crop opposite to the drag", () => {
const scale = minimumScale(image, VIEWPORT) * 2;
const left = sourceRect(image, VIEWPORT, { scale, offsetX: 40, offsetY: 0 });
const centre = sourceRect(image, VIEWPORT, { scale, offsetX: 0, offsetY: 0 });
expect(left.x).toBeLessThan(centre.x);
});
});
-109
View File
@@ -1,109 +0,0 @@
/**
* The geometry behind the circular photo editor.
*
* The editor shows a fixed square viewport with a circular mask; the image sits
* behind it, scaled and dragged. Everything here works in viewport pixels and
* is deliberately free of DOM and canvas, so the awkward parts - the smallest
* scale that still covers the circle, and clamping so no gap can be dragged
* into view - are testable on their own.
*/
export interface Size {
width: number;
height: number;
}
/** Where the image sits behind the viewport, and how big it is drawn. */
export interface Transform {
/** Multiplier applied to the image's natural size. */
scale: number;
/** Image centre, relative to the viewport centre, in viewport pixels. */
offsetX: number;
offsetY: number;
}
/**
* The smallest scale at which the image still covers the whole viewport.
*
* Below this a corner of the frame is empty, and the exported photo would carry
* a transparent wedge - so this is the floor for the zoom control, not merely a
* suggestion.
*/
export function minimumScale(image: Size, viewport: number): number {
// Finite, not merely positive: NaN fails every comparison, so `<= 0` lets it
// through and it then propagates into the transform and blanks the editor.
// A NaN dimension is what an image that failed to decode reports.
if (
!Number.isFinite(image.width) || !Number.isFinite(image.height) || !Number.isFinite(viewport) ||
image.width <= 0 || image.height <= 0 || viewport <= 0
) {
return 1;
}
// Cover, not contain: the larger of the two ratios.
return Math.max(viewport / image.width, viewport / image.height);
}
/** The zoom control's range: from covering the frame to four times that. */
export function scaleRange(image: Size, viewport: number): { min: number; max: number } {
const min = minimumScale(image, viewport);
return { min, max: min * 4 };
}
/**
* Holds the image over the viewport, whatever was asked for.
*
* A drag or a zoom out can otherwise pull an edge inside the frame. Clamping
* the offset to the overhang keeps the frame covered without fighting the
* gesture: the image stops at its edge instead of springing back.
*/
export function clampTransform(image: Size, viewport: number, wanted: Transform): Transform {
const { min, max } = scaleRange(image, viewport);
const scale = Math.min(Math.max(wanted.scale, min), max);
// Half of what sticks out past the viewport on each axis.
const overhangX = Math.max(0, (image.width * scale - viewport) / 2);
const overhangY = Math.max(0, (image.height * scale - viewport) / 2);
return {
scale,
offsetX: Math.min(Math.max(wanted.offsetX, -overhangX), overhangX),
offsetY: Math.min(Math.max(wanted.offsetY, -overhangY), overhangY),
};
}
/** The transform that centres the image at the smallest covering scale. */
export function initialTransform(image: Size, viewport: number): Transform {
return { scale: minimumScale(image, viewport), offsetX: 0, offsetY: 0 };
}
/**
* The rectangle of the *source* image that the viewport is showing.
*
* This is what the export draws. Working back to source pixels rather than
* scaling the preview means the saved photo is as sharp as the original
* allows, instead of being limited to the size of the editor on screen.
*/
export interface SourceRect {
x: number;
y: number;
width: number;
height: number;
}
export function sourceRect(image: Size, viewport: number, transform: Transform): SourceRect {
const t = clampTransform(image, viewport, transform);
// One viewport pixel is this many source pixels.
const perPixel = 1 / t.scale;
const size = viewport * perPixel;
// The viewport centre, expressed in source coordinates.
const centreX = image.width / 2 - t.offsetX * perPixel;
const centreY = image.height / 2 - t.offsetY * perPixel;
return {
x: centreX - size / 2,
y: centreY - size / 2,
width: size,
height: size,
};
}