chore: Move to Typescript (#2783)

This PR moves the entire project to Typescript. Due to the ~1000 ignores this will lead to a messy codebase for a while, but the churn is worth it – all of those ignore comments are places that were never type-safe previously.

closes #1282
This commit is contained in:
Tom Moor
2021-11-29 06:40:55 -08:00
committed by GitHub
parent 25ccfb5d04
commit 15b1069bcc
1017 changed files with 17410 additions and 54942 deletions

View File

@@ -1,11 +0,0 @@
// @flow
import { ApiKey } from "../models";
export default function present(key: ApiKey) {
return {
id: key.id,
name: key.name,
secret: key.secret,
createdAt: key.createdAt,
};
}

View File

@@ -0,0 +1,11 @@
import { ApiKey } from "@server/models";
// @ts-expect-error ts-migrate(2749) FIXME: 'ApiKey' refers to a value, but is being used as a... Remove this comment to see the full error message
export default function present(key: ApiKey) {
return {
id: key.id,
name: key.name,
secret: key.secret,
createdAt: key.createdAt,
};
}

View File

@@ -1,7 +1,7 @@
// @flow
import { AuthenticationProvider } from "../models";
import { AuthenticationProvider } from "@server/models";
export default function present(
// @ts-expect-error ts-migrate(2749) FIXME: 'AuthenticationProvider' refers to a value, but is... Remove this comment to see the full error message
authenticationProvider: AuthenticationProvider
) {
return {

View File

@@ -1,25 +1,25 @@
// @flow
import naturalSort from "../../shared/utils/naturalSort";
import { Collection } from "../models";
import naturalSort from "@shared/utils/naturalSort";
import { Collection } from "@server/models";
type Document = {
children: Document[],
id: string,
title: string,
url: string,
children: Document[];
id: string;
title: string;
url: string;
};
// @ts-expect-error ts-migrate(7006) FIXME: Parameter 'sort' implicitly has an 'any' type.
const sortDocuments = (documents: Document[], sort): Document[] => {
const orderedDocs = naturalSort(documents, sort.field, {
direction: sort.direction,
});
return orderedDocs.map((document) => ({
...document,
children: sortDocuments(document.children, sort),
}));
};
// @ts-expect-error ts-migrate(2749) FIXME: 'Collection' refers to a value, but is being used ... Remove this comment to see the full error message
export default function present(collection: Collection) {
const data = {
id: collection.id,
@@ -41,7 +41,10 @@ export default function present(collection: Collection) {
// Handle the "sort" field being empty here for backwards compatability
if (!data.sort) {
data.sort = { field: "title", direction: "asc" };
data.sort = {
field: "title",
direction: "asc",
};
}
// "index" field is manually sorted and is represented by the documentStructure

View File

@@ -1,13 +1,13 @@
// @flow
import { CollectionGroup } from "../models";
import { CollectionGroup } from "@server/models";
type Membership = {
id: string,
groupId: string,
collectionId: string,
permission: string,
id: string;
groupId: string;
collectionId: string;
permission: string;
};
// @ts-expect-error ts-migrate(2749) FIXME: 'CollectionGroup' refers to a value, but is being ... Remove this comment to see the full error message
export default (membership: CollectionGroup): Membership => {
return {
id: `${membership.groupId}-${membership.collectionId}`,

View File

@@ -1,81 +0,0 @@
// @flow
import { Attachment, Document } from "../models";
import parseAttachmentIds from "../utils/parseAttachmentIds";
import { getSignedUrl } from "../utils/s3";
import presentUser from "./user";
type Options = {
isPublic?: boolean,
};
// replaces attachments.redirect urls with signed/authenticated url equivalents
async function replaceImageAttachments(text: string) {
const attachmentIds = parseAttachmentIds(text);
await Promise.all(
attachmentIds.map(async (id) => {
const attachment = await Attachment.findByPk(id);
if (attachment) {
const accessUrl = await getSignedUrl(attachment.key);
text = text.replace(attachment.redirectUrl, accessUrl);
}
})
);
return text;
}
export default async function present(document: Document, options: ?Options) {
options = {
isPublic: false,
...options,
};
await document.migrateVersion();
let text = options.isPublic
? await replaceImageAttachments(document.text)
: document.text;
const data = {
id: document.id,
url: document.url,
urlId: document.urlId,
title: document.title,
text,
emoji: document.emoji,
tasks: document.tasks,
createdAt: document.createdAt,
createdBy: undefined,
updatedAt: document.updatedAt,
updatedBy: undefined,
publishedAt: document.publishedAt,
archivedAt: document.archivedAt,
deletedAt: document.deletedAt,
teamId: document.teamId,
template: document.template,
templateId: document.templateId,
collaboratorIds: [],
starred: document.starred ? !!document.starred.length : undefined,
revision: document.revisionCount,
pinned: undefined,
collectionId: undefined,
parentDocumentId: undefined,
lastViewedAt: undefined,
};
if (!!document.views && document.views.length > 0) {
data.lastViewedAt = document.views[0].updatedAt;
}
if (!options.isPublic) {
data.pinned = !!document.pinnedById;
data.collectionId = document.collectionId;
data.parentDocumentId = document.parentDocumentId;
data.createdBy = presentUser(document.createdBy);
data.updatedBy = presentUser(document.updatedBy);
data.collaboratorIds = document.collaboratorIds;
}
return data;
}

View File

@@ -0,0 +1,106 @@
import { Attachment, Document } from "@server/models";
import parseAttachmentIds from "@server/utils/parseAttachmentIds";
import { getSignedUrl } from "@server/utils/s3";
import presentUser from "./user";
type Options = {
isPublic?: boolean;
};
// replaces attachments.redirect urls with signed/authenticated url equivalents
async function replaceImageAttachments(text: string) {
const attachmentIds = parseAttachmentIds(text);
await Promise.all(
attachmentIds.map(async (id) => {
const attachment = await Attachment.findByPk(id);
if (attachment) {
const accessUrl = await getSignedUrl(attachment.key);
text = text.replace(attachment.redirectUrl, accessUrl);
}
})
);
return text;
}
export default async function present(
document: Document,
options: Options | null | undefined
) {
options = {
isPublic: false,
...options,
};
// @ts-expect-error ts-migrate(2339) FIXME: Property 'migrateVersion' does not exist on type '... Remove this comment to see the full error message
await document.migrateVersion();
const text = options.isPublic
? // @ts-expect-error ts-migrate(2339) FIXME: Property 'text' does not exist on type 'Document'.
await replaceImageAttachments(document.text)
: // @ts-expect-error ts-migrate(2339) FIXME: Property 'text' does not exist on type 'Document'.
document.text;
const data = {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'id' does not exist on type 'Document'.
id: document.id,
// @ts-expect-error ts-migrate(2551) FIXME: Property 'url' does not exist on type 'Document'. ... Remove this comment to see the full error message
url: document.url,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'urlId' does not exist on type 'Document'... Remove this comment to see the full error message
urlId: document.urlId,
title: document.title,
text,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'emoji' does not exist on type 'Document'... Remove this comment to see the full error message
emoji: document.emoji,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'tasks' does not exist on type 'Document'... Remove this comment to see the full error message
tasks: document.tasks,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'createdAt' does not exist on type 'Docum... Remove this comment to see the full error message
createdAt: document.createdAt,
createdBy: undefined,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'updatedAt' does not exist on type 'Docum... Remove this comment to see the full error message
updatedAt: document.updatedAt,
updatedBy: undefined,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'publishedAt' does not exist on type 'Doc... Remove this comment to see the full error message
publishedAt: document.publishedAt,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'archivedAt' does not exist on type 'Docu... Remove this comment to see the full error message
archivedAt: document.archivedAt,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'deletedAt' does not exist on type 'Docum... Remove this comment to see the full error message
deletedAt: document.deletedAt,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'teamId' does not exist on type 'Document... Remove this comment to see the full error message
teamId: document.teamId,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'template' does not exist on type 'Docume... Remove this comment to see the full error message
template: document.template,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'templateId' does not exist on type 'Docu... Remove this comment to see the full error message
templateId: document.templateId,
collaboratorIds: [],
// @ts-expect-error ts-migrate(2339) FIXME: Property 'starred' does not exist on type 'Documen... Remove this comment to see the full error message
starred: document.starred ? !!document.starred.length : undefined,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'revisionCount' does not exist on type 'D... Remove this comment to see the full error message
revision: document.revisionCount,
pinned: undefined,
collectionId: undefined,
parentDocumentId: undefined,
lastViewedAt: undefined,
};
// @ts-expect-error ts-migrate(2339) FIXME: Property 'views' does not exist on type 'Document'... Remove this comment to see the full error message
if (!!document.views && document.views.length > 0) {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'views' does not exist on type 'Document'... Remove this comment to see the full error message
data.lastViewedAt = document.views[0].updatedAt;
}
if (!options.isPublic) {
// @ts-expect-error ts-migrate(2322) FIXME: Type 'boolean' is not assignable to type 'undefine... Remove this comment to see the full error message
data.pinned = !!document.pinnedById;
// @ts-expect-error ts-migrate(2339) FIXME: Property 'collectionId' does not exist on type 'Do... Remove this comment to see the full error message
data.collectionId = document.collectionId;
// @ts-expect-error ts-migrate(2339) FIXME: Property 'parentDocumentId' does not exist on type... Remove this comment to see the full error message
data.parentDocumentId = document.parentDocumentId;
// @ts-expect-error ts-migrate(2322) FIXME: Type 'UserPresentation | null | undefined' is not ... Remove this comment to see the full error message
data.createdBy = presentUser(document.createdBy);
// @ts-expect-error ts-migrate(2322) FIXME: Type 'UserPresentation | null | undefined' is not ... Remove this comment to see the full error message
data.updatedBy = presentUser(document.updatedBy);
// @ts-expect-error ts-migrate(2339) FIXME: Property 'collaboratorIds' does not exist on type ... Remove this comment to see the full error message
data.collaboratorIds = document.collaboratorIds;
}
return data;
}

View File

@@ -1,8 +1,8 @@
// @flow
import { PublicEnv } from "@shared/types";
// Note: This entire object is stringified in the HTML exposed to the client
// do not add anything here that should be a secret or password
export default function present(env: Object): Object {
export default function present(env: Record<string, any>): PublicEnv {
return {
URL: env.URL.replace(/\/$/, ""),
CDN_URL: (env.CDN_URL || "").replace(/\/$/, ""),

View File

@@ -1,24 +0,0 @@
// @flow
import { Event } from "../models";
import presentUser from "./user";
export default function present(event: Event, isAdmin: boolean = false) {
let data = {
id: event.id,
name: event.name,
modelId: event.modelId,
actorId: event.actorId,
actorIpAddress: event.ip,
collectionId: event.collectionId,
documentId: event.documentId,
createdAt: event.createdAt,
data: event.data,
actor: presentUser(event.actor),
};
if (!isAdmin) {
delete data.actorIpAddress;
}
return data;
}

View File

@@ -0,0 +1,33 @@
import { Event } from "@server/models";
import presentUser from "./user";
export default function present(event: Event, isAdmin = false) {
const data = {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'id' does not exist on type 'Event'.
id: event.id,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'name' does not exist on type 'Event'.
name: event.name,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'modelId' does not exist on type 'Event'.
modelId: event.modelId,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'actorId' does not exist on type 'Event'.
actorId: event.actorId,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'ip' does not exist on type 'Event'.
actorIpAddress: event.ip,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'collectionId' does not exist on type 'Ev... Remove this comment to see the full error message
collectionId: event.collectionId,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'documentId' does not exist on type 'Even... Remove this comment to see the full error message
documentId: event.documentId,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'createdAt' does not exist on type 'Event... Remove this comment to see the full error message
createdAt: event.createdAt,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'data' does not exist on type 'Event'.
data: event.data,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'actor' does not exist on type 'Event'.
actor: presentUser(event.actor),
};
if (!isAdmin) {
delete data.actorIpAddress;
}
return data;
}

View File

@@ -1,7 +1,7 @@
// @flow
import { FileOperation } from "../models";
import { FileOperation } from "@server/models";
import { presentCollection, presentUser } from ".";
// @ts-expect-error ts-migrate(2749) FIXME: 'FileOperation' refers to a value, but is being us... Remove this comment to see the full error message
export default function present(data: FileOperation) {
return {
id: data.id,

View File

@@ -1,6 +1,6 @@
// @flow
import { Group } from "../models";
import { Group } from "@server/models";
// @ts-expect-error ts-migrate(2749) FIXME: 'Group' refers to a value, but is being used as a ... Remove this comment to see the full error message
export default function present(group: Group) {
return {
id: group.id,

View File

@@ -1,18 +0,0 @@
// @flow
import { GroupUser } from "../models";
import { presentUser } from ".";
type GroupMembership = {
id: string,
userId: string,
groupId: string,
};
export default (membership: GroupUser): GroupMembership => {
return {
id: `${membership.userId}-${membership.groupId}`,
userId: membership.userId,
groupId: membership.groupId,
user: presentUser(membership.user),
};
};

View File

@@ -0,0 +1,19 @@
import { GroupUser } from "@server/models";
import { presentUser } from ".";
type GroupMembership = {
id: string;
userId: string;
groupId: string;
};
// @ts-expect-error ts-migrate(2749) FIXME: 'GroupUser' refers to a value, but is being used a... Remove this comment to see the full error message
export default (membership: GroupUser): GroupMembership => {
return {
id: `${membership.userId}-${membership.groupId}`,
userId: membership.userId,
groupId: membership.groupId,
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ id: string; userId: any; groupId: any; use... Remove this comment to see the full error message
user: presentUser(membership.user),
};
};

View File

@@ -1,4 +1,3 @@
// @flow
import presentApiKey from "./apiKey";
import presentAuthenticationProvider from "./authenticationProvider";
import presentCollection from "./collection";

View File

@@ -1,6 +1,6 @@
// @flow
import { Integration } from "../models";
import { Integration } from "@server/models";
// @ts-expect-error ts-migrate(2749) FIXME: 'Integration' refers to a value, but is being used... Remove this comment to see the full error message
export default function present(integration: Integration) {
return {
id: integration.id,

View File

@@ -1,13 +1,13 @@
// @flow
import { CollectionUser } from "../models";
import { CollectionUser } from "@server/models";
type Membership = {
id: string,
userId: string,
collectionId: string,
permission: string,
id: string;
userId: string;
collectionId: string;
permission: string;
};
// @ts-expect-error ts-migrate(2749) FIXME: 'CollectionUser' refers to a value, but is being u... Remove this comment to see the full error message
export default (membership: CollectionUser): Membership => {
return {
id: `${membership.userId}-${membership.collectionId}`,

View File

@@ -1,9 +0,0 @@
// @flow
import { NotificationSetting } from "../models";
export default function present(setting: NotificationSetting) {
return {
id: setting.id,
event: setting.event,
};
}

View File

@@ -0,0 +1,9 @@
import { NotificationSetting } from "@server/models";
// @ts-expect-error ts-migrate(2749) FIXME: 'NotificationSetting' refers to a value, but is be... Remove this comment to see the full error message
export default function present(setting: NotificationSetting) {
return {
id: setting.id,
event: setting.event,
};
}

View File

@@ -1,13 +0,0 @@
// @flow
import { User } from "../models";
type Policy = { id: string, abilities: { [key: string]: boolean } };
export default function present(user: User, objects: Object[]): Policy[] {
const { serialize } = require("../policies");
return objects.map((object) => ({
id: object.id,
abilities: serialize(user, object),
}));
}

View File

@@ -0,0 +1,20 @@
import { User } from "@server/models";
type Policy = {
id: string;
abilities: Record<string, boolean>;
};
export default function present(
// @ts-expect-error ts-migrate(2749) FIXME: 'User' refers to a value, but is being used as a t... Remove this comment to see the full error message
user: User,
objects: Record<string, any>[]
): Policy[] {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { serialize } = require("../policies");
return objects.map((object) => ({
id: object.id,
abilities: serialize(user, object),
}));
}

View File

@@ -1,10 +1,9 @@
// @flow
import { Revision } from "../models";
import { Revision } from "@server/models";
import presentUser from "./user";
// @ts-expect-error ts-migrate(2749) FIXME: 'Revision' refers to a value, but is being used as... Remove this comment to see the full error message
export default async function present(revision: Revision) {
await revision.migrateVersion();
return {
id: revision.id,
documentId: revision.documentId,

View File

@@ -1,9 +1,9 @@
// @flow
import { Share } from "../models";
import { Share } from "@server/models";
import { presentUser } from ".";
export default function present(share: Share, isAdmin: boolean = false) {
let data = {
// @ts-expect-error ts-migrate(2749) FIXME: 'Share' refers to a value, but is being used as a ... Remove this comment to see the full error message
export default function present(share: Share, isAdmin = false) {
const data = {
id: share.id,
documentId: share.documentId,
documentTitle: share.document.title,

View File

@@ -1,34 +0,0 @@
// @flow
import { Document, Collection, Team } from "../models";
type Action = {
type: string,
text: string,
name: string,
value: string,
};
export default function present(
document: Document,
collection: Collection,
team: Team,
context?: string,
actions?: Action[]
) {
// the context contains <b> tags around search terms, we convert them here
// to the markdown format that slack expects to receive.
const text = context
? context.replace(/<\/?b>/g, "*").replace(/\n/g, "")
: document.getSummary();
return {
color: collection.color,
title: document.title,
title_link: `${team.url}${document.url}`,
footer: collection.name,
callback_id: document.id,
text,
ts: document.getTimestamp(),
actions,
};
}

View File

@@ -0,0 +1,39 @@
import { Document, Collection, Team } from "@server/models";
type Action = {
type: string;
text: string;
name: string;
value: string;
};
export default function present(
document: Document,
// @ts-expect-error ts-migrate(2749) FIXME: 'Collection' refers to a value, but is being used ... Remove this comment to see the full error message
collection: Collection,
// @ts-expect-error ts-migrate(2749) FIXME: 'Team' refers to a value, but is being used as a t... Remove this comment to see the full error message
team: Team,
context?: string,
actions?: Action[]
) {
// the context contains <b> tags around search terms, we convert them here
// to the markdown format that slack expects to receive.
const text = context
? context.replace(/<\/?b>/g, "*").replace(/\n/g, "")
: // @ts-expect-error ts-migrate(2339) FIXME: Property 'getSummary' does not exist on type 'Docu... Remove this comment to see the full error message
document.getSummary();
return {
color: collection.color,
title: document.title,
// @ts-expect-error ts-migrate(2551) FIXME: Property 'url' does not exist on type 'Document'. ... Remove this comment to see the full error message
title_link: `${team.url}${document.url}`,
footer: collection.name,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'id' does not exist on type 'Document'.
callback_id: document.id,
text,
// @ts-expect-error ts-migrate(2339) FIXME: Property 'getTimestamp' does not exist on type 'Do... Remove this comment to see the full error message
ts: document.getTimestamp(),
actions,
};
}

View File

@@ -1,6 +1,6 @@
// @flow
import { Team } from "../models";
import { Team } from "@server/models";
// @ts-expect-error ts-migrate(2749) FIXME: 'Team' refers to a value, but is being used as a t... Remove this comment to see the full error message
export default function present(team: Team) {
return {
id: team.id,

View File

@@ -1,39 +0,0 @@
// @flow
import { User } from "../models";
type Options = {
includeDetails?: boolean,
};
type UserPresentation = {
id: string,
name: string,
avatarUrl: ?string,
email?: string,
color: string,
isAdmin: boolean,
isSuspended: boolean,
isViewer: boolean,
language: string,
};
export default (user: User, options: Options = {}): ?UserPresentation => {
const userData = {};
userData.id = user.id;
userData.createdAt = user.createdAt;
userData.name = user.name;
userData.color = user.color;
userData.isAdmin = user.isAdmin;
userData.isViewer = user.isViewer;
userData.isSuspended = user.isSuspended;
userData.avatarUrl = user.avatarUrl;
userData.lastActiveAt = user.lastActiveAt;
if (options.includeDetails) {
userData.email = user.email;
userData.language =
user.language || process.env.DEFAULT_LANGUAGE || "en_US";
}
return userData;
};

View File

@@ -1,4 +1,3 @@
/* eslint-disable flowtype/require-valid-file-annotation */
import presentUser from "./user";
it("presents a user", async () => {
@@ -7,16 +6,13 @@ it("presents a user", async () => {
name: "Test User",
username: "testuser",
});
expect(user).toMatchSnapshot();
});
it("presents a user without slack data", async () => {
const user = presentUser({
id: "123",
name: "Test User",
username: "testuser",
});
expect(user).toMatchSnapshot();
});

53
server/presenters/user.ts Normal file
View File

@@ -0,0 +1,53 @@
import { User } from "@server/models";
type Options = {
includeDetails?: boolean;
};
type UserPresentation = {
id: string;
name: string;
avatarUrl: string | null | undefined;
email?: string;
color: string;
isAdmin: boolean;
isSuspended: boolean;
isViewer: boolean;
language: string;
};
export default (
// @ts-expect-error ts-migrate(2749) FIXME: 'User' refers to a value, but is being used as a t... Remove this comment to see the full error message
user: User,
options: Options = {}
): UserPresentation | null | undefined => {
const userData = {};
// @ts-expect-error ts-migrate(2339) FIXME: Property 'id' does not exist on type '{}'.
userData.id = user.id;
// @ts-expect-error ts-migrate(2339) FIXME: Property 'createdAt' does not exist on type '{}'.
userData.createdAt = user.createdAt;
// @ts-expect-error ts-migrate(2339) FIXME: Property 'name' does not exist on type '{}'.
userData.name = user.name;
// @ts-expect-error ts-migrate(2339) FIXME: Property 'color' does not exist on type '{}'.
userData.color = user.color;
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isAdmin' does not exist on type '{}'.
userData.isAdmin = user.isAdmin;
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isViewer' does not exist on type '{}'.
userData.isViewer = user.isViewer;
// @ts-expect-error ts-migrate(2339) FIXME: Property 'isSuspended' does not exist on type '{}'... Remove this comment to see the full error message
userData.isSuspended = user.isSuspended;
// @ts-expect-error ts-migrate(2339) FIXME: Property 'avatarUrl' does not exist on type '{}'.
userData.avatarUrl = user.avatarUrl;
// @ts-expect-error ts-migrate(2339) FIXME: Property 'lastActiveAt' does not exist on type '{}... Remove this comment to see the full error message
userData.lastActiveAt = user.lastActiveAt;
if (options.includeDetails) {
// @ts-expect-error ts-migrate(2339) FIXME: Property 'email' does not exist on type '{}'.
userData.email = user.email;
// @ts-expect-error ts-migrate(2339) FIXME: Property 'language' does not exist on type '{}'.
userData.language =
user.language || process.env.DEFAULT_LANGUAGE || "en_US";
}
// @ts-expect-error ts-migrate(2740) FIXME: Type '{}' is missing the following properties from... Remove this comment to see the full error message
return userData;
};

View File

@@ -1,7 +1,7 @@
// @flow
import { View } from "../models";
import { View } from "@server/models";
import { presentUser } from "../presenters";
// @ts-expect-error ts-migrate(2749) FIXME: 'View' refers to a value, but is being used as a t... Remove this comment to see the full error message
export default function present(view: View) {
return {
id: view.id,