[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
No known key found for this signature in database
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 { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js";
import { ListHelpers } from "@/server/api/mastodon/helpers/list.js";
import { Files } from "formidable";
export function setupEndpointsAccount(router: Router): void {
router.get("/v1/accounts/verify_credentials", async (ctx) => {
@ -40,7 +41,8 @@ export function setupEndpointsAccount(router: Router): void {
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)
} catch (e: any) {
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?
const files = (ctx.request as any).files as Files;
const file = toSingleLast(files['file']);
const files = (ctx.request as any).files as Files | undefined;
const file = toSingleLast(files?.file);
if (!file) {
ctx.body = {error: "No image"};
ctx.status = 400;

View File

@ -7,7 +7,7 @@ import { File } from "formidable";
export class MediaHelpers {
public static async uploadMedia(user: ILocalUser, file: File, body: any): Promise<Packed<"DriveFile">> {
return await addFile({
return addFile({
user: user,
path: file.filepath,
name: file.originalFilename !== null && file.originalFilename !== 'file' ? file.originalFilename : undefined,
@ -17,6 +17,15 @@ export class MediaHelpers {
.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">> {
await DriveFiles.update(file.id, {
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 { Brackets, IsNull } from "typeorm";
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 = {
locks: AsyncLock;
@ -53,6 +59,7 @@ export type updateCredsData = {
locked: boolean;
bot: boolean;
discoverable: boolean;
fields_attributes?: { name: string, value: string }[];
}
type RelationshipType = 'followers' | 'following';
@ -145,17 +152,41 @@ export class UserHelpers {
return this.getUserRelationshipTo(target.id, localUser.id);
}
public static async updateCredentials(user: ILocalUser, formData: updateCredsData): Promise<MastodonEntity.Account> {
//FIXME: Actually implement this
//FIXME: handle multipart avatar & header image upload
//FIXME: handle field attributes
const obj: any = {};
public static async updateCredentials(user: ILocalUser, formData: updateCredsData, files: Files | undefined): Promise<MastodonEntity.Account> {
const updates: Partial<User> = {};
const profileUpdates: Partial<UserProfile> = {};
if (formData.display_name) obj.name = formData.display_name;
if (formData.note) obj.description = formData.note;
if (formData.locked) obj.isLocked = formData.locked;
if (formData.bot) obj.isBot = formData.bot;
if (formData.discoverable) obj.isExplorable = formData.discoverable;
const avatar = toSingleLast(files?.avatar);
const header = toSingleLast(files?.header);
if (avatar) {
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);
}
@ -164,10 +195,16 @@ export class UserHelpers {
const acct = UserConverter.encode(user);
const profile = UserProfiles.findOneByOrFail({userId: user.id});
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 => {
const source = {
note: acct.note,
fields: acct.fields,
note: profile.then(profile => profile.description ?? ''),
fields: fields,
privacy: privacy.then(p => VisibilityConverter.encode(p)),
sensitive: profile.then(p => p.alwaysMarkNsfw),
language: profile.then(p => p.lang ?? ''),