[mastodon-client] PATCH /v1/accounts/update_credentials

This commit is contained in:
Laura Hausmann 2023-10-05 18:58:37 +02:00
parent 75f6732928
commit 01b5af2686
Signed by: zotan
GPG key ID: D044E84C5BE01605
4 changed files with 64 additions and 16 deletions

View file

@ -9,6 +9,7 @@ import { NoteConverter } from "@/server/api/mastodon/converters/note.js";
import { UserHelpers } from "@/server/api/mastodon/helpers/user.js"; import { UserHelpers } from "@/server/api/mastodon/helpers/user.js";
import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js"; import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js";
import { ListHelpers } from "@/server/api/mastodon/helpers/list.js"; import { ListHelpers } from "@/server/api/mastodon/helpers/list.js";
import { Files } from "formidable";
export function setupEndpointsAccount(router: Router): void { export function setupEndpointsAccount(router: Router): void {
router.get("/v1/accounts/verify_credentials", async (ctx) => { router.get("/v1/accounts/verify_credentials", async (ctx) => {
@ -40,7 +41,8 @@ export function setupEndpointsAccount(router: Router): void {
return; return;
} }
const acct = await UserHelpers.updateCredentials(user, (ctx.request as any).body as any); const files = (ctx.request as any).files as Files | undefined;
const acct = await UserHelpers.updateCredentials(user, (ctx.request as any).body as any, files);
ctx.body = convertAccountId(acct) ctx.body = convertAccountId(acct)
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);

View file

@ -75,8 +75,8 @@ export function setupEndpointsMedia(router: Router): void {
} }
//FIXME: why do we have to cast this to any first? //FIXME: why do we have to cast this to any first?
const files = (ctx.request as any).files as Files; const files = (ctx.request as any).files as Files | undefined;
const file = toSingleLast(files['file']); const file = toSingleLast(files?.file);
if (!file) { if (!file) {
ctx.body = {error: "No image"}; ctx.body = {error: "No image"};
ctx.status = 400; ctx.status = 400;

View file

@ -7,7 +7,7 @@ import { File } from "formidable";
export class MediaHelpers { export class MediaHelpers {
public static async uploadMedia(user: ILocalUser, file: File, body: any): Promise<Packed<"DriveFile">> { public static async uploadMedia(user: ILocalUser, file: File, body: any): Promise<Packed<"DriveFile">> {
return await addFile({ return addFile({
user: user, user: user,
path: file.filepath, path: file.filepath,
name: file.originalFilename !== null && file.originalFilename !== 'file' ? file.originalFilename : undefined, name: file.originalFilename !== null && file.originalFilename !== 'file' ? file.originalFilename : undefined,
@ -17,6 +17,15 @@ export class MediaHelpers {
.then(p => DriveFiles.pack(p)); .then(p => DriveFiles.pack(p));
} }
public static async uploadMediaBasic(user: ILocalUser, file: File): Promise<DriveFile> {
return addFile({
user: user,
path: file.filepath,
name: file.originalFilename !== null && file.originalFilename !== 'file' ? file.originalFilename : undefined,
sensitive: false
})
}
public static async updateMedia(user: ILocalUser, file: DriveFile, body: any): Promise<Packed<"DriveFile">> { public static async updateMedia(user: ILocalUser, file: DriveFile, body: any): Promise<Packed<"DriveFile">> {
await DriveFiles.update(file.id, { await DriveFiles.update(file.id, {
comment: body?.description ?? undefined comment: body?.description ?? undefined

View file

@ -34,6 +34,12 @@ import acceptFollowRequest from "@/services/following/requests/accept.js";
import { rejectFollowRequest } from "@/services/following/reject.js"; import { rejectFollowRequest } from "@/services/following/reject.js";
import { Brackets, IsNull } from "typeorm"; import { Brackets, IsNull } from "typeorm";
import { IceshrimpVisibility, VisibilityConverter } from "@/server/api/mastodon/converters/visibility.js"; import { IceshrimpVisibility, VisibilityConverter } from "@/server/api/mastodon/converters/visibility.js";
import { Files } from "formidable";
import { toSingleLast } from "@/prelude/array.js";
import { MediaHelpers } from "@/server/api/mastodon/helpers/media.js";
import { FileConverter } from "@/server/api/mastodon/converters/file.js";
import { UserProfile } from "@/models/entities/user-profile.js";
import { verifyLink } from "@/services/fetch-rel-me.js";
export type AccountCache = { export type AccountCache = {
locks: AsyncLock; locks: AsyncLock;
@ -53,6 +59,7 @@ export type updateCredsData = {
locked: boolean; locked: boolean;
bot: boolean; bot: boolean;
discoverable: boolean; discoverable: boolean;
fields_attributes?: { name: string, value: string }[];
} }
type RelationshipType = 'followers' | 'following'; type RelationshipType = 'followers' | 'following';
@ -145,17 +152,41 @@ export class UserHelpers {
return this.getUserRelationshipTo(target.id, localUser.id); return this.getUserRelationshipTo(target.id, localUser.id);
} }
public static async updateCredentials(user: ILocalUser, formData: updateCredsData): Promise<MastodonEntity.Account> { public static async updateCredentials(user: ILocalUser, formData: updateCredsData, files: Files | undefined): Promise<MastodonEntity.Account> {
//FIXME: Actually implement this const updates: Partial<User> = {};
//FIXME: handle multipart avatar & header image upload const profileUpdates: Partial<UserProfile> = {};
//FIXME: handle field attributes
const obj: any = {};
if (formData.display_name) obj.name = formData.display_name; const avatar = toSingleLast(files?.avatar);
if (formData.note) obj.description = formData.note; const header = toSingleLast(files?.header);
if (formData.locked) obj.isLocked = formData.locked;
if (formData.bot) obj.isBot = formData.bot; if (avatar) {
if (formData.discoverable) obj.isExplorable = formData.discoverable; const file = await MediaHelpers.uploadMediaBasic(user, avatar);
updates.avatarId = file.id;
}
if (header) {
const file = await MediaHelpers.uploadMediaBasic(user, header);
updates.bannerId = file.id;
}
if (formData.fields_attributes) {
profileUpdates.fields = await Promise.all(formData.fields_attributes.map(async field => {
const verified = field.value.startsWith("http") ? await verifyLink(field.value, user.username) : undefined;
return {
...field,
verified
};
}));
}
if (formData.display_name) updates.name = formData.display_name;
if (formData.note) profileUpdates.description = formData.note;
if (formData.locked) updates.isLocked = formData.locked;
if (formData.bot) updates.isBot = formData.bot;
if (formData.discoverable) updates.isExplorable = formData.discoverable;
if (Object.keys(updates).length > 0) await Users.update(user.id, updates);
if (Object.keys(profileUpdates).length > 0) await UserProfiles.update({ userId: user.id }, profileUpdates);
return this.verifyCredentials(user); return this.verifyCredentials(user);
} }
@ -164,10 +195,16 @@ export class UserHelpers {
const acct = UserConverter.encode(user); const acct = UserConverter.encode(user);
const profile = UserProfiles.findOneByOrFail({userId: user.id}); const profile = UserProfiles.findOneByOrFail({userId: user.id});
const privacy = this.getDefaultNoteVisibility(user); const privacy = this.getDefaultNoteVisibility(user);
const fields = profile.then(profile => profile.fields.map(field => {
return {
name: field.name,
value: field.value
} as MastodonEntity.Field;
}));
return acct.then(acct => { return acct.then(acct => {
const source = { const source = {
note: acct.note, note: profile.then(profile => profile.description ?? ''),
fields: acct.fields, fields: fields,
privacy: privacy.then(p => VisibilityConverter.encode(p)), privacy: privacy.then(p => VisibilityConverter.encode(p)),
sensitive: profile.then(p => p.alwaysMarkNsfw), sensitive: profile.then(p => p.alwaysMarkNsfw),
language: profile.then(p => p.lang ?? ''), language: profile.then(p => p.lang ?? ''),