mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 16:38:19 +01:00
refactor(database): ♻️ Make user avatar and header into a Media instead of plaintext
This commit is contained in:
parent
bc961b70bb
commit
ba431e2b11
|
|
@ -1,16 +1,14 @@
|
||||||
import { apiRoute, auth, jsonOrForm } from "@/api";
|
import { apiRoute, auth, jsonOrForm } from "@/api";
|
||||||
import { mimeLookup } from "@/content_types";
|
|
||||||
import { mergeAndDeduplicate } from "@/lib";
|
import { mergeAndDeduplicate } from "@/lib";
|
||||||
import { sanitizedHtmlStrip } from "@/sanitization";
|
import { sanitizedHtmlStrip } from "@/sanitization";
|
||||||
import { createRoute } from "@hono/zod-openapi";
|
import { createRoute } from "@hono/zod-openapi";
|
||||||
import { Emoji, Media, User } from "@versia/kit/db";
|
import { Emoji, User } from "@versia/kit/db";
|
||||||
import { RolePermissions, Users } from "@versia/kit/tables";
|
import { RolePermissions, Users } from "@versia/kit/tables";
|
||||||
import { and, eq, isNull } from "drizzle-orm";
|
import { and, eq, isNull } from "drizzle-orm";
|
||||||
import ISO6391 from "iso-639-1";
|
import ISO6391 from "iso-639-1";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ApiError } from "~/classes/errors/api-error";
|
import { ApiError } from "~/classes/errors/api-error";
|
||||||
import { contentToHtml } from "~/classes/functions/status";
|
import { contentToHtml } from "~/classes/functions/status";
|
||||||
import { MediaManager } from "~/classes/media/media-manager";
|
|
||||||
import { config } from "~/packages/config-manager/index.ts";
|
import { config } from "~/packages/config-manager/index.ts";
|
||||||
import { ErrorSchema } from "~/types/api";
|
import { ErrorSchema } from "~/types/api";
|
||||||
|
|
||||||
|
|
@ -206,8 +204,6 @@ export default apiRoute((app) =>
|
||||||
display_name ?? "",
|
display_name ?? "",
|
||||||
);
|
);
|
||||||
|
|
||||||
const mediaManager = new MediaManager(config);
|
|
||||||
|
|
||||||
if (display_name) {
|
if (display_name) {
|
||||||
self.displayName = sanitizedDisplayName;
|
self.displayName = sanitizedDisplayName;
|
||||||
}
|
}
|
||||||
|
|
@ -249,37 +245,17 @@ export default apiRoute((app) =>
|
||||||
|
|
||||||
if (avatar) {
|
if (avatar) {
|
||||||
if (avatar instanceof File) {
|
if (avatar instanceof File) {
|
||||||
const { path, uploadedFile } =
|
await user.avatar?.updateFromFile(avatar);
|
||||||
await mediaManager.addFile(avatar);
|
|
||||||
const contentType = uploadedFile.type;
|
|
||||||
|
|
||||||
self.avatar = Media.getUrl(path);
|
|
||||||
self.source.avatar = {
|
|
||||||
content_type: contentType,
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
self.avatar = avatar.toString();
|
await user.avatar?.updateFromUrl(avatar);
|
||||||
self.source.avatar = {
|
|
||||||
content_type: await mimeLookup(avatar),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header) {
|
if (header) {
|
||||||
if (header instanceof File) {
|
if (header instanceof File) {
|
||||||
const { path, uploadedFile } =
|
await user.header?.updateFromFile(header);
|
||||||
await mediaManager.addFile(header);
|
|
||||||
const contentType = uploadedFile.type;
|
|
||||||
|
|
||||||
self.header = Media.getUrl(path);
|
|
||||||
self.source.header = {
|
|
||||||
content_type: contentType,
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
self.header = header.toString();
|
await user.header?.updateFromUrl(header);
|
||||||
self.source.header = {
|
|
||||||
content_type: await mimeLookup(header),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,7 @@ export default apiRoute((app) =>
|
||||||
app.openapi(route, async (context) => {
|
app.openapi(route, async (context) => {
|
||||||
const { user } = context.get("auth");
|
const { user } = context.get("auth");
|
||||||
|
|
||||||
await user.update({
|
await user.header?.delete();
|
||||||
avatar: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
return context.json(user.toApi(true), 200);
|
return context.json(user.toApi(true), 200);
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,7 @@ export default apiRoute((app) =>
|
||||||
app.openapi(route, async (context) => {
|
app.openapi(route, async (context) => {
|
||||||
const { user } = context.get("auth");
|
const { user } = context.get("auth");
|
||||||
|
|
||||||
await user.update({
|
await user.header?.delete();
|
||||||
header: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
return context.json(user.toApi(true), 200);
|
return context.json(user.toApi(true), 200);
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,8 @@ export class Media extends BaseInterface<typeof Medias> {
|
||||||
} else {
|
} else {
|
||||||
await db.delete(Medias).where(eq(Medias.id, this.id));
|
await db.delete(Medias).where(eq(Medias.id, this.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Also delete the file from the media manager
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async insert(
|
public static async insert(
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import type {
|
||||||
FollowReject as VersiaFollowReject,
|
FollowReject as VersiaFollowReject,
|
||||||
User as VersiaUser,
|
User as VersiaUser,
|
||||||
} from "@versia/federation/types";
|
} from "@versia/federation/types";
|
||||||
import { Notification, PushSubscription, db } from "@versia/kit/db";
|
import { Media, Notification, PushSubscription, db } from "@versia/kit/db";
|
||||||
import {
|
import {
|
||||||
EmojiToUser,
|
EmojiToUser,
|
||||||
Likes,
|
Likes,
|
||||||
|
|
@ -69,6 +69,8 @@ type UserWithInstance = InferSelectModel<typeof Users> & {
|
||||||
|
|
||||||
type UserWithRelations = UserWithInstance & {
|
type UserWithRelations = UserWithInstance & {
|
||||||
emojis: (typeof Emoji.$type)[];
|
emojis: (typeof Emoji.$type)[];
|
||||||
|
avatar: typeof Media.$type | null;
|
||||||
|
header: typeof Media.$type | null;
|
||||||
followerCount: number;
|
followerCount: number;
|
||||||
followingCount: number;
|
followingCount: number;
|
||||||
statusCount: number;
|
statusCount: number;
|
||||||
|
|
@ -149,6 +151,16 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
|
|
||||||
public static $type: UserWithRelations;
|
public static $type: UserWithRelations;
|
||||||
|
|
||||||
|
public avatar: Media | null;
|
||||||
|
public header: Media | null;
|
||||||
|
|
||||||
|
public constructor(data: UserWithRelations) {
|
||||||
|
super(data);
|
||||||
|
|
||||||
|
this.avatar = data.avatar ? new Media(data.avatar) : null;
|
||||||
|
this.header = data.header ? new Media(data.header) : null;
|
||||||
|
}
|
||||||
|
|
||||||
public async reload(): Promise<void> {
|
public async reload(): Promise<void> {
|
||||||
const reloaded = await User.fromId(this.data.id);
|
const reloaded = await User.fromId(this.data.id);
|
||||||
|
|
||||||
|
|
@ -728,9 +740,6 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
user: VersiaUser,
|
user: VersiaUser,
|
||||||
instance: Instance,
|
instance: Instance,
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
const avatar = user.avatar ? Object.entries(user.avatar)[0] : null;
|
|
||||||
const header = user.header ? Object.entries(user.header)[0] : null;
|
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
username: user.username,
|
username: user.username,
|
||||||
uri: user.uri,
|
uri: user.uri,
|
||||||
|
|
@ -748,8 +757,6 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
fields: user.fields ?? [],
|
fields: user.fields ?? [],
|
||||||
updatedAt: new Date(user.created_at).toISOString(),
|
updatedAt: new Date(user.created_at).toISOString(),
|
||||||
instanceId: instance.id,
|
instanceId: instance.id,
|
||||||
avatar: avatar?.[1].content || "",
|
|
||||||
header: header?.[1].content || "",
|
|
||||||
displayName: user.display_name ?? "",
|
displayName: user.display_name ?? "",
|
||||||
note: getBestContentType(user.bio).content,
|
note: getBestContentType(user.bio).content,
|
||||||
publicKey: user.public_key.key,
|
publicKey: user.public_key.key,
|
||||||
|
|
@ -759,16 +766,6 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
privacy: "public",
|
privacy: "public",
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
fields: [],
|
fields: [],
|
||||||
avatar: avatar
|
|
||||||
? {
|
|
||||||
content_type: avatar[0],
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
header: header
|
|
||||||
? {
|
|
||||||
content_type: header[0],
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -784,14 +781,65 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
|
|
||||||
// If it exists, simply update it
|
// If it exists, simply update it
|
||||||
if (foundUser) {
|
if (foundUser) {
|
||||||
await foundUser.update(data);
|
let avatar: Media | null = null;
|
||||||
|
let header: Media | null = null;
|
||||||
|
|
||||||
|
if (user.avatar) {
|
||||||
|
if (foundUser.avatar) {
|
||||||
|
avatar = new Media(
|
||||||
|
await foundUser.avatar.update({
|
||||||
|
content: user.avatar,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
avatar = await Media.insert({
|
||||||
|
content: user.avatar,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.header) {
|
||||||
|
if (foundUser.header) {
|
||||||
|
header = new Media(
|
||||||
|
await foundUser.header.update({
|
||||||
|
content: user.header,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
header = await Media.insert({
|
||||||
|
content: user.header,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await foundUser.update({
|
||||||
|
...data,
|
||||||
|
avatarId: avatar?.id,
|
||||||
|
headerId: header?.id,
|
||||||
|
});
|
||||||
await foundUser.updateEmojis(emojis);
|
await foundUser.updateEmojis(emojis);
|
||||||
|
|
||||||
return foundUser;
|
return foundUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Else, create a new user
|
// Else, create a new user
|
||||||
const newUser = await User.insert(data);
|
const avatar = user.avatar
|
||||||
|
? await Media.insert({
|
||||||
|
content: user.avatar,
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const header = user.header
|
||||||
|
? await Media.insert({
|
||||||
|
content: user.header,
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const newUser = await User.insert({
|
||||||
|
...data,
|
||||||
|
avatarId: avatar?.id,
|
||||||
|
headerId: header?.id,
|
||||||
|
});
|
||||||
await newUser.updateEmojis(emojis);
|
await newUser.updateEmojis(emojis);
|
||||||
|
|
||||||
return newUser;
|
return newUser;
|
||||||
|
|
@ -846,13 +894,13 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
* @returns The raw URL for the user's avatar
|
* @returns The raw URL for the user's avatar
|
||||||
*/
|
*/
|
||||||
public getAvatarUrl(config: Config): string {
|
public getAvatarUrl(config: Config): string {
|
||||||
if (!this.data.avatar) {
|
if (!this.avatar) {
|
||||||
return (
|
return (
|
||||||
config.defaults.avatar ||
|
config.defaults.avatar ||
|
||||||
`https://api.dicebear.com/8.x/${config.defaults.placeholder_style}/svg?seed=${this.data.username}`
|
`https://api.dicebear.com/8.x/${config.defaults.placeholder_style}/svg?seed=${this.data.username}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return this.data.avatar;
|
return this.avatar?.getUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async generateKeys(): Promise<{
|
public static async generateKeys(): Promise<{
|
||||||
|
|
@ -886,14 +934,8 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
password: string | undefined;
|
password: string | undefined;
|
||||||
email: string | undefined;
|
email: string | undefined;
|
||||||
bio?: string;
|
bio?: string;
|
||||||
avatar?: {
|
avatar?: Media;
|
||||||
url: string;
|
header?: Media;
|
||||||
content_type: string;
|
|
||||||
};
|
|
||||||
header?: {
|
|
||||||
url: string;
|
|
||||||
content_type: string;
|
|
||||||
};
|
|
||||||
admin?: boolean;
|
admin?: boolean;
|
||||||
skipPasswordHash?: boolean;
|
skipPasswordHash?: boolean;
|
||||||
}): Promise<User> {
|
}): Promise<User> {
|
||||||
|
|
@ -911,8 +953,8 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
: await Bun.password.hash(data.password),
|
: await Bun.password.hash(data.password),
|
||||||
email: data.email,
|
email: data.email,
|
||||||
note: data.bio ?? "",
|
note: data.bio ?? "",
|
||||||
avatar: data.avatar?.url ?? config.defaults.avatar ?? "",
|
avatarId: data.avatar?.id,
|
||||||
header: data.header?.url ?? config.defaults.avatar ?? "",
|
headerId: data.header?.id,
|
||||||
isAdmin: data.admin ?? false,
|
isAdmin: data.admin ?? false,
|
||||||
publicKey: keys.public_key,
|
publicKey: keys.public_key,
|
||||||
fields: [],
|
fields: [],
|
||||||
|
|
@ -924,16 +966,6 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
privacy: "public",
|
privacy: "public",
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
fields: [],
|
fields: [],
|
||||||
avatar: data.avatar
|
|
||||||
? {
|
|
||||||
content_type: data.avatar.content_type,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
header: data.header
|
|
||||||
? {
|
|
||||||
content_type: data.header.content_type,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.returning()
|
.returning()
|
||||||
|
|
@ -957,10 +989,10 @@ export class User extends BaseInterface<typeof Users, UserWithRelations> {
|
||||||
* @returns The raw URL for the user's header
|
* @returns The raw URL for the user's header
|
||||||
*/
|
*/
|
||||||
public getHeaderUrl(config: Config): string {
|
public getHeaderUrl(config: Config): string {
|
||||||
if (!this.data.header) {
|
if (!this.header) {
|
||||||
return config.defaults.header || "";
|
return config.defaults.header || "";
|
||||||
}
|
}
|
||||||
return this.data.header;
|
return this.header.getUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAcct(): string {
|
public getAcct(): string {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import {
|
||||||
type Application,
|
type Application,
|
||||||
type Emoji,
|
type Emoji,
|
||||||
type Instance,
|
type Instance,
|
||||||
|
type Media,
|
||||||
type Role,
|
type Role,
|
||||||
type Token,
|
type Token,
|
||||||
type User,
|
type User,
|
||||||
|
|
@ -22,6 +23,8 @@ export const userRelations = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
avatar: true,
|
||||||
|
header: true,
|
||||||
roles: {
|
roles: {
|
||||||
with: {
|
with: {
|
||||||
role: true,
|
role: true,
|
||||||
|
|
@ -76,6 +79,8 @@ export const transformOutputToUserWithRelations = (
|
||||||
followerCount: unknown;
|
followerCount: unknown;
|
||||||
followingCount: unknown;
|
followingCount: unknown;
|
||||||
statusCount: unknown;
|
statusCount: unknown;
|
||||||
|
avatar: typeof Media.$type | null;
|
||||||
|
header: typeof Media.$type | null;
|
||||||
emojis: {
|
emojis: {
|
||||||
userId: string;
|
userId: string;
|
||||||
emojiId: string;
|
emojiId: string;
|
||||||
|
|
|
||||||
6
drizzle/migrations/0045_polite_mikhail_rasputin.sql
Normal file
6
drizzle/migrations/0045_polite_mikhail_rasputin.sql
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
ALTER TABLE "Users" ADD COLUMN "avatarId" uuid;--> statement-breakpoint
|
||||||
|
ALTER TABLE "Users" ADD COLUMN "headerId" uuid;--> statement-breakpoint
|
||||||
|
ALTER TABLE "Users" ADD CONSTRAINT "Users_avatarId_Medias_id_fk" FOREIGN KEY ("avatarId") REFERENCES "public"."Medias"("id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint
|
||||||
|
ALTER TABLE "Users" ADD CONSTRAINT "Users_headerId_Medias_id_fk" FOREIGN KEY ("headerId") REFERENCES "public"."Medias"("id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint
|
||||||
|
ALTER TABLE "Users" DROP COLUMN "avatar";--> statement-breakpoint
|
||||||
|
ALTER TABLE "Users" DROP COLUMN "header";
|
||||||
2349
drizzle/migrations/meta/0045_snapshot.json
Normal file
2349
drizzle/migrations/meta/0045_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -316,6 +316,13 @@
|
||||||
"when": 1738082427051,
|
"when": 1738082427051,
|
||||||
"tag": "0044_quiet_jasper_sitwell",
|
"tag": "0044_quiet_jasper_sitwell",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 45,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1738087527661,
|
||||||
|
"tag": "0045_polite_mikhail_rasputin",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -365,6 +365,12 @@ export const Medias = pgTable("Medias", {
|
||||||
export const MediasRelations = relations(Medias, ({ many }) => ({
|
export const MediasRelations = relations(Medias, ({ many }) => ({
|
||||||
notes: many(Notes),
|
notes: many(Notes),
|
||||||
emojis: many(Emojis),
|
emojis: many(Emojis),
|
||||||
|
avatars: many(Users, {
|
||||||
|
relationName: "UserToAvatar",
|
||||||
|
}),
|
||||||
|
headers: many(Users, {
|
||||||
|
relationName: "UserToHeader",
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const Notifications = pgTable("Notifications", {
|
export const Notifications = pgTable("Notifications", {
|
||||||
|
|
@ -557,8 +563,14 @@ export const Users = pgTable(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
>(),
|
>(),
|
||||||
avatar: text("avatar").notNull(),
|
avatarId: uuid("avatarId").references(() => Medias.id, {
|
||||||
header: text("header").notNull(),
|
onDelete: "set null",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
|
headerId: uuid("headerId").references(() => Medias.id, {
|
||||||
|
onDelete: "set null",
|
||||||
|
onUpdate: "cascade",
|
||||||
|
}),
|
||||||
createdAt: createdAt(),
|
createdAt: createdAt(),
|
||||||
updatedAt: updatedAt(),
|
updatedAt: updatedAt(),
|
||||||
isBot: boolean("is_bot").default(false).notNull(),
|
isBot: boolean("is_bot").default(false).notNull(),
|
||||||
|
|
@ -588,6 +600,16 @@ export const UsersRelations = relations(Users, ({ many, one }) => ({
|
||||||
notes: many(Notes, {
|
notes: many(Notes, {
|
||||||
relationName: "NoteToAuthor",
|
relationName: "NoteToAuthor",
|
||||||
}),
|
}),
|
||||||
|
avatar: one(Medias, {
|
||||||
|
fields: [Users.avatarId],
|
||||||
|
references: [Medias.id],
|
||||||
|
relationName: "UserToAvatar",
|
||||||
|
}),
|
||||||
|
header: one(Medias, {
|
||||||
|
fields: [Users.headerId],
|
||||||
|
references: [Medias.id],
|
||||||
|
relationName: "UserToHeader",
|
||||||
|
}),
|
||||||
likes: many(Likes),
|
likes: many(Likes),
|
||||||
relationships: many(Relationships, {
|
relationships: many(Relationships, {
|
||||||
relationName: "RelationshipToOwner",
|
relationName: "RelationshipToOwner",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { mimeLookup } from "@/content_types.ts";
|
|
||||||
import { randomString } from "@/math.ts";
|
import { randomString } from "@/math.ts";
|
||||||
import { createRoute, z } from "@hono/zod-openapi";
|
import { createRoute, z } from "@hono/zod-openapi";
|
||||||
import { Token, User, db } from "@versia/kit/db";
|
import { Media, Token, User, db } from "@versia/kit/db";
|
||||||
import { type SQL, and, eq, isNull } from "@versia/kit/drizzle";
|
import { type SQL, and, eq, isNull } from "@versia/kit/drizzle";
|
||||||
import { OpenIdAccounts, RolePermissions, Users } from "@versia/kit/tables";
|
import { OpenIdAccounts, RolePermissions, Users } from "@versia/kit/tables";
|
||||||
import { setCookie } from "hono/cookie";
|
import { setCookie } from "hono/cookie";
|
||||||
|
|
@ -243,18 +242,15 @@ export default (plugin: PluginType): void => {
|
||||||
? !!(await User.fromSql(eq(Users.email, email)))
|
? !!(await User.fromSql(eq(Users.email, email)))
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
|
const avatar = picture
|
||||||
|
? await Media.fromUrl(new URL(picture))
|
||||||
|
: null;
|
||||||
|
|
||||||
// Create new user
|
// Create new user
|
||||||
const user = await User.fromDataLocal({
|
const user = await User.fromDataLocal({
|
||||||
email: doesEmailExist ? undefined : email,
|
email: doesEmailExist ? undefined : email,
|
||||||
username,
|
username,
|
||||||
avatar: picture
|
avatar: avatar ?? undefined,
|
||||||
? {
|
|
||||||
url: picture,
|
|
||||||
content_type: await mimeLookup(
|
|
||||||
new URL(picture),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
password: undefined,
|
password: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue