Remove usage of tiley (#4406)

* First pass

* Mooarrr

* lint

* snapshots
This commit is contained in:
Tom Moor
2022-11-08 17:12:22 -08:00
committed by GitHub
parent 920b58c006
commit 587f062677
38 changed files with 169 additions and 177 deletions

View File

@ -1,7 +1,9 @@
import { PlusIcon } from "outline-icons";
import * as React from "react";
import styled from "styled-components";
import { stringToColor } from "@shared/utils/color";
import TeamNew from "~/scenes/TeamNew";
import TeamLogo from "~/components/TeamLogo";
import { createAction } from "~/actions";
import { loadSessionsFromCookie } from "~/hooks/useSessions";
import { TeamSection } from "../sections";
@ -11,7 +13,18 @@ export const switchTeamList = getSessions().map((session) => {
name: session.name,
section: TeamSection,
keywords: "change switch workspace organization team",
icon: () => <Logo alt={session.name} src={session.logoUrl} />,
icon: () => (
<StyledTeamLogo
alt={session.name}
model={{
initial: session.name[0],
avatarUrl: session.logoUrl,
id: session.teamId,
color: stringToColor(session.teamId),
}}
size={24}
/>
),
visible: ({ currentTeamId }) => currentTeamId !== session.teamId,
perform: () => (window.location.href = session.url),
});
@ -55,10 +68,9 @@ function getSessions(params?: { exclude?: string }) {
return otherSessions;
}
const Logo = styled("img")`
const StyledTeamLogo = styled(TeamLogo)`
border-radius: 2px;
width: 24px;
height: 24px;
border: 0;
`;
export const rootTeamActions = [switchTeam, createTeam];

View File

@ -1,14 +1,21 @@
import * as React from "react";
import styled from "styled-components";
import User from "~/models/User";
import useBoolean from "~/hooks/useBoolean";
import Initials from "./Initials";
import placeholder from "./placeholder.png";
export interface IAvatar {
avatarUrl: string | null;
color: string;
initial: string;
id: string;
}
type Props = {
src: string;
size: number;
src?: string;
icon?: React.ReactNode;
user?: User;
model?: IAvatar;
alt?: string;
showBorder?: boolean;
onClick?: React.MouseEventHandler<HTMLImageElement>;
@ -16,20 +23,28 @@ type Props = {
};
function Avatar(props: Props) {
const { src, icon, showBorder, ...rest } = props;
const { icon, showBorder, model, ...rest } = props;
const src = props.src || model?.avatarUrl;
const [error, handleError] = useBoolean(false);
return (
<AvatarWrapper>
<CircleImg
onError={handleError}
src={error ? placeholder : src}
$showBorder={showBorder}
{...rest}
/>
<Relative>
{src ? (
<CircleImg
onError={handleError}
src={error ? placeholder : src}
$showBorder={showBorder}
{...rest}
/>
) : model ? (
<Initials color={model.color} $showBorder={showBorder} {...rest}>
{model.initial}
</Initials>
) : (
<Initials $showBorder={showBorder} {...rest} />
)}
{icon && <IconWrapper>{icon}</IconWrapper>}
</AvatarWrapper>
</Relative>
);
}
@ -37,7 +52,7 @@ Avatar.defaultProps = {
size: 24,
};
const AvatarWrapper = styled.div`
const Relative = styled.div`
position: relative;
`;

View File

@ -51,7 +51,7 @@ function AvatarWithPresence({
$isObserving={isObserving}
$color={user.color}
>
<Avatar src={user.avatarUrl} onClick={onClick} size={32} />
<Avatar model={user} onClick={onClick} size={32} />
</AvatarWrapper>
</Tooltip>
</>

View File

@ -0,0 +1,27 @@
import styled from "styled-components";
import Flex from "~/components/Flex";
const Initials = styled(Flex)<{
color?: string;
size: number;
$showBorder?: boolean;
}>`
align-items: center;
justify-content: center;
border-radius: 50%;
width: 100%;
height: 100%;
color: #fff;
background-color: ${(props) => props.color};
width: ${(props) => props.size}px;
height: ${(props) => props.size}px;
border-radius: 50%;
border: 2px solid
${(props) =>
props.$showBorder === false ? "transparent" : props.theme.background};
flex-shrink: 0;
font-size: ${(props) => props.size / 2}px;
font-weight: 500;
`;
export default Initials;

View File

@ -43,10 +43,10 @@ function DocumentViews({ document, isOpen }: Props) {
<PaginatedList
aria-label={t("Viewers")}
items={users}
renderItem={(item: User) => {
const view = documentViews.find((v) => v.user.id === item.id);
const isPresent = presentIds.includes(item.id);
const isEditing = editingIds.includes(item.id);
renderItem={(model: User) => {
const view = documentViews.find((v) => v.user.id === model.id);
const isPresent = presentIds.includes(model.id);
const isEditing = editingIds.includes(model.id);
const subtitle = isPresent
? isEditing
? t("Currently editing")
@ -58,10 +58,10 @@ function DocumentViews({ document, isOpen }: Props) {
});
return (
<ListItem
key={item.id}
title={item.name}
key={model.id}
title={model.name}
subtitle={subtitle}
image={<Avatar key={item.id} src={item.avatarUrl} size={32} />}
image={<Avatar key={model.id} model={model} size={32} />}
border={false}
small
/>

View File

@ -142,7 +142,7 @@ const EventListItem = ({ event, latest, document, ...rest }: Props) => {
onClick={handleTimeClick}
/>
}
image={<Avatar src={event.actor?.avatarUrl} size={32} />}
image={<Avatar model={event.actor} size={32} />}
subtitle={
<Subtitle>
{icon}

View File

@ -39,7 +39,7 @@ function Facepile({
}
function DefaultAvatar(user: User) {
return <Avatar user={user} src={user.avatarUrl} size={32} />;
return <Avatar model={user} size={32} />;
}
const AvatarWrapper = styled.div`

View File

@ -63,14 +63,7 @@ function AppSidebar() {
<SidebarButton
{...props}
title={team.name}
image={
<StyledTeamLogo
src={team.avatarUrl}
width={32}
height={32}
alt={t("Logo")}
/>
}
image={<TeamLogo model={team} size={32} alt={t("Logo")} />}
showDisclosure
/>
)}
@ -139,11 +132,6 @@ function AppSidebar() {
);
}
const StyledTeamLogo = styled(TeamLogo)`
margin-right: 4px;
background: white;
`;
const Drafts = styled(Text)`
margin: 0 4px;
`;

View File

@ -178,7 +178,7 @@ const Sidebar = React.forwardRef<HTMLDivElement, Props>(
image={
<StyledAvatar
alt={user.name}
src={user.avatarUrl}
model={user}
size={24}
showBorder={false}
/>

View File

@ -1,10 +1,7 @@
import styled from "styled-components";
import Avatar from "./Avatar";
const TeamLogo = styled.img<{ width?: number; height?: number; size?: string }>`
width: ${(props) =>
props.width ? `${props.width}px` : props.size || "auto"};
height: ${(props) =>
props.height ? `${props.height}px` : props.size || "38px"};
const TeamLogo = styled(Avatar)`
border-radius: 4px;
border: 1px solid ${(props) => props.theme.divider};
overflow: hidden;

View File

@ -1,5 +1,6 @@
import { computed, observable } from "mobx";
import { TeamPreference, TeamPreferences } from "@shared/types";
import { stringToColor } from "@shared/utils/color";
import BaseModel from "./BaseModel";
import Field from "./decorators/Field";
@ -69,6 +70,16 @@ class Team extends BaseModel {
return "SSO";
}
@computed
get color(): string {
return stringToColor(this.id);
}
@computed
get initial(): string {
return this.name ? this.name[0] : "?";
}
/**
* Returns whether this team is using a separate editing mode behind an "Edit"
* button rather than seamless always-editing.

View File

@ -40,6 +40,11 @@ class User extends ParanoidModel {
isSuspended: boolean;
@computed
get initial(): string {
return this.name ? this.name[0] : "?";
}
@computed
get isInvited(): boolean {
return !this.lastActiveAt;

View File

@ -104,18 +104,16 @@ const MembershipPreview = ({ collection, limit = 8 }: Props) => {
users={sortBy(collectionUsers, "lastActiveAt")}
overflow={overflow}
limit={limit}
renderAvatar={(user) => (
<StyledAvatar user={user} src={user.avatarUrl} size={32} />
)}
renderAvatar={(user) => <StyledAvatar model={user} size={32} />}
/>
</Fade>
</NudeButton>
);
};
const StyledAvatar = styled(Avatar)<{ user: User }>`
const StyledAvatar = styled(Avatar)<{ model: User }>`
transition: opacity 250ms ease-in-out;
opacity: ${(props) => (props.user.isRecentlyActive ? 1 : 0.5)};
opacity: ${(props) => (props.model.isRecentlyActive ? 1 : 0.5)};
`;
export default observer(MembershipPreview);

View File

@ -49,7 +49,7 @@ const MemberListItem = ({
{user.isAdmin && <Badge primary={user.isAdmin}>{t("Admin")}</Badge>}
</>
}
image={<Avatar src={user.avatarUrl} size={32} />}
image={<Avatar model={user} size={32} />}
actions={
<Flex align="center" gap={8}>
{onUpdate && (

View File

@ -21,7 +21,7 @@ const UserListItem = ({ user, onAdd, canEdit }: Props) => {
return (
<ListItem
title={user.name}
image={<Avatar src={user.avatarUrl} size={32} />}
image={<Avatar model={user} size={32} />}
subtitle={
<>
{user.lastActiveAt ? (

View File

@ -35,7 +35,7 @@ const GroupMemberListItem = ({ user, onRemove, onAdd }: Props) => {
{user.isAdmin && <Badge primary={user.isAdmin}>{t("Admin")}</Badge>}
</>
}
image={<Avatar src={user.avatarUrl} size={32} />}
image={<Avatar model={user} size={32} />}
actions={
<Flex align="center">
{onRemove && <GroupMemberMenu onRemove={onRemove} />}

View File

@ -21,7 +21,7 @@ const UserListItem = ({ user, onAdd, canEdit }: Props) => {
return (
<ListItem
title={user.name}
image={<Avatar src={user.avatarUrl} size={32} />}
image={<Avatar model={user} size={32} />}
subtitle={
<>
{user.lastActiveAt ? (

View File

@ -169,7 +169,7 @@ function Login({ children }: Props) {
/>
<Logo>
{config.logo ? (
<TeamLogo width={48} height={48} src={config.logo} />
<TeamLogo size={48} src={config.logo} />
) : (
<OutlineLogo size={42} fill="currentColor" />
)}

View File

@ -26,7 +26,6 @@ function Details() {
const form = useRef<HTMLFormElement>(null);
const [name, setName] = useState(team.name);
const [subdomain, setSubdomain] = useState(team.subdomain);
const [avatarUrl, setAvatarUrl] = useState<string>(team.avatarUrl);
const [defaultCollectionId, setDefaultCollectionId] = useState<string | null>(
team.defaultCollectionId
);
@ -40,7 +39,6 @@ function Details() {
try {
await auth.updateTeam({
name,
avatarUrl,
subdomain,
defaultCollectionId,
});
@ -53,7 +51,7 @@ function Details() {
});
}
},
[auth, name, avatarUrl, subdomain, defaultCollectionId, showToast, t]
[auth, name, subdomain, defaultCollectionId, showToast, t]
);
const handleNameChange = React.useCallback(
@ -71,7 +69,6 @@ function Details() {
);
const handleAvatarUpload = async (avatarUrl: string) => {
setAvatarUrl(avatarUrl);
await auth.updateTeam({
avatarUrl,
});
@ -115,7 +112,7 @@ function Details() {
<ImageInput
onSuccess={handleAvatarUpload}
onError={handleAvatarError}
src={avatarUrl}
model={team}
borderRadius={0}
/>
</SettingRow>

View File

@ -18,7 +18,6 @@ const Profile = () => {
const user = useCurrentUser();
const form = React.useRef<HTMLFormElement>(null);
const [name, setName] = React.useState<string>(user.name || "");
const [avatarUrl, setAvatarUrl] = React.useState<string>(user.avatarUrl);
const { showToast } = useToasts();
const { t } = useTranslation();
@ -28,7 +27,6 @@ const Profile = () => {
try {
await auth.updateUser({
name,
avatarUrl,
});
showToast(t("Profile saved"), {
type: "success",
@ -45,7 +43,6 @@ const Profile = () => {
};
const handleAvatarUpload = async (avatarUrl: string) => {
setAvatarUrl(avatarUrl);
await auth.updateUser({
avatarUrl,
});
@ -79,7 +76,7 @@ const Profile = () => {
<ImageInput
onSuccess={handleAvatarUpload}
onError={handleAvatarError}
src={avatarUrl}
model={user}
/>
</SettingRow>
<SettingRow

View File

@ -1,21 +1,22 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import Avatar, { IAvatar } from "~/components/Avatar/Avatar";
import Flex from "~/components/Flex";
import ImageUpload, { Props as ImageUploadProps } from "./ImageUpload";
type Props = ImageUploadProps & {
src?: string;
model: IAvatar;
};
export default function ImageInput({ src, ...rest }: Props) {
export default function ImageInput({ model, ...rest }: Props) {
const { t } = useTranslation();
return (
<ImageBox>
<ImageUpload {...rest}>
<Avatar src={src} />
<Flex auto align="center" justify="center">
<StyledAvatar model={model} size={64} />
<Flex auto align="center" justify="center" className="upload">
{t("Upload")}
</Flex>
</ImageUpload>
@ -28,8 +29,8 @@ const avatarStyles = `
height: 64px;
`;
const Avatar = styled.img`
${avatarStyles};
const StyledAvatar = styled(Avatar)`
border-radius: 8px;
`;
const ImageBox = styled(Flex)`
@ -41,7 +42,7 @@ const ImageBox = styled(Flex)`
background: ${(props) => props.theme.background};
overflow: hidden;
div div {
.upload {
${avatarStyles};
position: absolute;
top: 0;
@ -53,7 +54,7 @@ const ImageBox = styled(Flex)`
transition: all 250ms;
}
&:hover div {
&:hover .upload {
opacity: 1;
background: rgba(0, 0, 0, 0.75);
color: ${(props) => props.theme.white};

View File

@ -29,7 +29,7 @@ function PeopleTable({ canManage, ...rest }: Props) {
Cell: observer(
({ value, row }: { value: string; row: { original: User } }) => (
<Flex align="center" gap={8}>
<Avatar src={row.original.avatarUrl} size={32} /> {value}{" "}
<Avatar model={row.original} size={32} /> {value}{" "}
{currentUser.id === row.original.id && `(${t("You")})`}
</Flex>
)

View File

@ -39,7 +39,7 @@ function SharesTable({ canManage, ...rest }: Props) {
<Flex align="center" gap={4}>
{row.original.createdBy && (
<Avatar
src={row.original.createdBy.avatarUrl}
model={row.original.createdBy}
alt={row.original.createdBy.name}
/>
)}

View File

@ -20,7 +20,7 @@ const UserListItem = ({ user, showMenu }: Props) => {
return (
<ListItem
title={<Title>{user.name}</Title>}
image={<Avatar src={user.avatarUrl} size={32} />}
image={<Avatar model={user} size={32} />}
subtitle={
<>
{user.email ? `${user.email} · ` : undefined}

View File

@ -39,7 +39,7 @@ function UserProfile(props: Props) {
<Modal
title={
<Flex align="center">
<Avatar src={user.avatarUrl} size={38} alt={t("Profile picture")} />
<Avatar model={user} size={38} alt={t("Profile picture")} />
<span>&nbsp;{user.name}</span>
</Flex>
}

View File

@ -226,14 +226,17 @@ export default class AuthStore {
preferences?: UserPreferences;
}) => {
this.isSaving = true;
const previousData = this.user?.toAPI();
try {
this.user?.updateFromJson(params);
const res = await client.post(`/users.update`, params);
invariant(res?.data, "User response not available");
runInAction("AuthStore#updateUser", () => {
this.addPolicies(res.policies);
this.user = new User(res.data, this);
});
this.user?.updateFromJson(res.data);
this.addPolicies(res.policies);
} catch (err) {
this.user?.updateFromJson(previousData);
throw err;
} finally {
this.isSaving = false;
}
@ -251,14 +254,17 @@ export default class AuthStore {
preferences?: TeamPreferences;
}) => {
this.isSaving = true;
const previousData = this.team?.toAPI();
try {
this.team?.updateFromJson(params);
const res = await client.post(`/team.update`, params);
invariant(res?.data, "Team response not available");
runInAction("AuthStore#updateTeam", () => {
this.addPolicies(res.policies);
this.team = new Team(res.data, this);
});
this.team?.updateFromJson(res.data);
this.addPolicies(res.policies);
} catch (err) {
this.team?.updateFromJson(previousData);
throw err;
} finally {
this.isSaving = false;
}

View File

@ -40,7 +40,6 @@ async function teamCreator({
// one via ClearBit, or fallback to colored initials in worst case scenario
if (!avatarUrl || !avatarUrl.startsWith("http")) {
avatarUrl = await generateAvatarUrl({
name,
domain,
id: subdomain,
});

View File

@ -329,13 +329,6 @@ export class Environment {
*/
public RELEASE = this.toOptionalString(process.env.RELEASE);
/**
* An optional host from which to load default avatars.
*/
@IsUrl()
public DEFAULT_AVATAR_HOST =
process.env.DEFAULT_AVATAR_HOST ?? "https://tiley.herokuapp.com";
/**
* A Google Analytics tracking ID, only v3 supported at this time.
*/

View File

@ -24,7 +24,6 @@ import { CollectionPermission, TeamPreference } from "@shared/types";
import { getBaseDomain, RESERVED_SUBDOMAINS } from "@shared/utils/domains";
import env from "@server/env";
import DeleteAttachmentTask from "@server/queues/tasks/DeleteAttachmentTask";
import { generateAvatarUrl } from "@server/utils/avatars";
import parseAttachmentIds from "@server/utils/parseAttachmentIds";
import Attachment from "./Attachment";
import AuthenticationProvider from "./AuthenticationProvider";
@ -94,8 +93,20 @@ class Team extends ParanoidModel {
@AllowNull
@IsUrl
@Length({ max: 4096, msg: "avatarUrl must be 4096 characters or less" })
@Column
avatarUrl: string | null;
@Column(DataType.STRING)
get avatarUrl() {
const original = this.getDataValue("avatarUrl");
if (original && !original.startsWith("https://tiley.herokuapp.com")) {
return original;
}
return null;
}
set avatarUrl(value: string | null) {
this.setDataValue("avatarUrl", value);
}
@Default(true)
@Column
@ -163,16 +174,6 @@ class Team extends ParanoidModel {
return url.href.replace(/\/$/, "");
}
get logoUrl() {
return (
this.avatarUrl ||
generateAvatarUrl({
id: this.id,
name: this.name,
})
);
}
/**
* Preferences that decide behavior for the team.
*

View File

@ -180,17 +180,11 @@ class User extends ParanoidModel {
get avatarUrl() {
const original = this.getDataValue("avatarUrl");
if (original) {
if (original && !original.startsWith("https://tiley.herokuapp.com")) {
return original;
}
const color = this.color.replace(/^#/, "");
const initial = this.name ? this.name[0] : "?";
const hash = crypto
.createHash("md5")
.update(this.email || "")
.digest("hex");
return `${env.DEFAULT_AVATAR_HOST}/avatar/${hash}/${initial}.png?c=${color}`;
return null;
}
set avatarUrl(value: string | null) {

View File

@ -2,7 +2,7 @@
exports[`presents a user 1`] = `
Object {
"avatarUrl": "https://tiley.herokuapp.com/avatar/d41d8cd98f00b204e9800998ecf8427e/T.png?c=FF5C80",
"avatarUrl": null,
"color": "#FF5C80",
"createdAt": undefined,
"id": "123",
@ -17,7 +17,7 @@ Object {
exports[`presents a user without slack data 1`] = `
Object {
"avatarUrl": "https://tiley.herokuapp.com/avatar/d41d8cd98f00b204e9800998ecf8427e/T.png?c=FF5C80",
"avatarUrl": null,
"color": "#FF5C80",
"createdAt": undefined,
"id": "123",

View File

@ -4,7 +4,7 @@ export default function present(team: Team, isSignedIn = false) {
return {
id: team.id,
name: team.name,
avatarUrl: team.logoUrl,
avatarUrl: team.avatarUrl,
url: team.url,
isSignedIn,
};

View File

@ -4,7 +4,7 @@ export default function present(team: Team) {
return {
id: team.id,
name: team.name,
avatarUrl: team.logoUrl,
avatarUrl: team.avatarUrl,
sharing: team.sharing,
memberCollectionCreate: team.memberCollectionCreate,
collaborativeEditing: team.collaborativeEditing,

View File

@ -3,7 +3,7 @@
exports[`#users.activate should activate a suspended user 1`] = `
Object {
"data": Object {
"avatarUrl": "https://tiley.herokuapp.com/avatar/111d68d06e2d317b5a59c2c6c5bad808/U.png?c=e600e0",
"avatarUrl": null,
"color": "#e600e0",
"createdAt": "2018-01-02T00:00:00.000Z",
"email": "user1@example.com",
@ -59,7 +59,7 @@ Object {
exports[`#users.demote should demote an admin 1`] = `
Object {
"data": Object {
"avatarUrl": "https://tiley.herokuapp.com/avatar/111d68d06e2d317b5a59c2c6c5bad808/U.png?c=e600e0",
"avatarUrl": null,
"color": "#e600e0",
"createdAt": "2018-01-02T00:00:00.000Z",
"email": "user1@example.com",
@ -97,7 +97,7 @@ Object {
exports[`#users.demote should demote an admin to member 1`] = `
Object {
"data": Object {
"avatarUrl": "https://tiley.herokuapp.com/avatar/111d68d06e2d317b5a59c2c6c5bad808/U.png?c=e600e0",
"avatarUrl": null,
"color": "#e600e0",
"createdAt": "2018-01-02T00:00:00.000Z",
"email": "user1@example.com",
@ -135,7 +135,7 @@ Object {
exports[`#users.demote should demote an admin to viewer 1`] = `
Object {
"data": Object {
"avatarUrl": "https://tiley.herokuapp.com/avatar/111d68d06e2d317b5a59c2c6c5bad808/U.png?c=e600e0",
"avatarUrl": null,
"color": "#e600e0",
"createdAt": "2018-01-02T00:00:00.000Z",
"email": "user1@example.com",
@ -191,7 +191,7 @@ Object {
exports[`#users.promote should promote a new admin 1`] = `
Object {
"data": Object {
"avatarUrl": "https://tiley.herokuapp.com/avatar/111d68d06e2d317b5a59c2c6c5bad808/U.png?c=e600e0",
"avatarUrl": null,
"color": "#e600e0",
"createdAt": "2018-01-02T00:00:00.000Z",
"email": "user1@example.com",
@ -256,7 +256,7 @@ Object {
exports[`#users.suspend should suspend an user 1`] = `
Object {
"data": Object {
"avatarUrl": "https://tiley.herokuapp.com/avatar/111d68d06e2d317b5a59c2c6c5bad808/U.png?c=e600e0",
"avatarUrl": null,
"color": "#e600e0",
"createdAt": "2018-01-02T00:00:00.000Z",
"email": "user1@example.com",

View File

@ -93,7 +93,7 @@ export async function signIn(
...existing,
[team.id]: {
name: team.name,
logoUrl: team.logoUrl,
logoUrl: team.avatarUrl,
url: team.url,
},
})

View File

@ -4,43 +4,6 @@ it("should return clearbit url if available", async () => {
const url = await generateAvatarUrl({
id: "google",
domain: "google.com",
name: "Google",
});
expect(url).toBe("https://logo.clearbit.com/google.com");
});
it("should return tiley url if clearbit unavailable", async () => {
const url = await generateAvatarUrl({
id: "invalid",
domain: "example.invalid",
name: "Invalid",
});
expect(url).toBe(
"https://tiley.herokuapp.com/avatar/f1234d75178d892a133a410355a5a990cf75d2f33eba25d575943d4df632f3a4/I.png"
);
});
it("should return tiley url if domain not provided", async () => {
const url = await generateAvatarUrl({
id: "google",
name: "Google",
});
expect(url).toBe(
"https://tiley.herokuapp.com/avatar/bbdefa2950f49882f295b1285d4fa9dec45fc4144bfb07ee6acc68762d12c2e3/G.png"
);
});
it("should return tiley url if name not provided", async () => {
const url = await generateAvatarUrl({
id: "google",
});
expect(url).toBe(
"https://tiley.herokuapp.com/avatar/bbdefa2950f49882f295b1285d4fa9dec45fc4144bfb07ee6acc68762d12c2e3/U.png"
);
});
it("should return tiley url with encoded name", async () => {
const url = await generateAvatarUrl({
id: "google",
name: "株",
});
expect(url).toBe(
"https://tiley.herokuapp.com/avatar/bbdefa2950f49882f295b1285d4fa9dec45fc4144bfb07ee6acc68762d12c2e3/%E6%A0%AA.png"
);
});

View File

@ -1,21 +1,17 @@
import crypto from "crypto";
import fetch from "fetch-with-proxy";
import env from "@server/env";
export async function generateAvatarUrl({
id,
domain,
name = "Unknown",
}: {
id: string;
domain?: string;
name?: string;
}) {
// attempt to get logo from Clearbit API. If one doesn't exist then
// fall back to using tiley to generate a placeholder logo
const hash = crypto.createHash("sha256");
hash.update(id);
const hashedId = hash.digest("hex");
let cbResponse, cbUrl;
if (domain) {
@ -28,8 +24,5 @@ export async function generateAvatarUrl({
}
}
const tileyUrl = `${
env.DEFAULT_AVATAR_HOST
}/avatar/${hashedId}/${encodeURIComponent(name[0])}.png`;
return cbUrl && cbResponse && cbResponse.status === 200 ? cbUrl : tileyUrl;
return cbUrl && cbResponse && cbResponse.status === 200 ? cbUrl : null;
}

View File

@ -6,7 +6,6 @@ import fetch from "fetch-with-proxy";
import { compact } from "lodash";
import { useAgent } from "request-filtering-agent";
import { v4 as uuidv4 } from "uuid";
import env from "@server/env";
import Logger from "@server/logging/Logger";
const AWS_S3_ACCELERATE_URL = process.env.AWS_S3_ACCELERATE_URL;
@ -184,11 +183,7 @@ export const uploadToS3FromUrl = async (
acl: string
) => {
const endpoint = publicS3Endpoint(true);
if (
url.startsWith("/api") ||
url.startsWith(endpoint) ||
url.startsWith(env.DEFAULT_AVATAR_HOST)
) {
if (url.startsWith("/api") || url.startsWith(endpoint)) {
return;
}