Skip to content

Commit

Permalink
お気に入り登録クリップの一覧画面から登録解除できるように (yojo-art#448)
Browse files Browse the repository at this point in the history
  • Loading branch information
kozakura913 authored Sep 17, 2024
1 parent 7949122 commit de66bbf
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 122 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG_YOJO.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ Cherrypick 4.11.1

### General
- Enhance: 連合一覧のソートにリバーシのバージョンを追加
- Enhance: リモートのクリップをお気に入りに登録できるように

### Client
- Fix: リアクションが閲覧できる状態でも見れない問題を修正 [#429](https://github.com/yojo-art/cherrypick/pull/429)
- Enhance: チャートの連合グラフで割合を表示
- Enhance: お気に入り登録クリップの一覧画面から登録解除できるように

### Server
-
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project, yojo-art team
* SPDX-License-Identifier: AGPL-3.0-only
*/

export class clipFavoriteRemoteAuthor1726460877945 {
name = 'clipFavoriteRemoteAuthor1726460877945'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "clip_favorite_remote" ADD "authorId" character varying(32)`);
await queryRunner.query(`ALTER TABLE "clip_favorite_remote" ADD CONSTRAINT "FK_e306e7566fd6101e9767702980c" FOREIGN KEY ("authorId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "clip_favorite_remote" DROP CONSTRAINT "FK_e306e7566fd6101e9767702980c"`);
await queryRunner.query(`ALTER TABLE "clip_favorite_remote" DROP COLUMN "authorId"`);
}
}
28 changes: 27 additions & 1 deletion packages/backend/src/core/ClipService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { bindThis } from '@/decorators.js';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
import { RoleService } from '@/core/RoleService.js';
import { IdService } from '@/core/IdService.js';
import type { MiLocalUser } from '@/models/User.js';
import type { MiLocalUser, MiUser } from '@/models/User.js';
import { Packed } from '@/misc/json-schema.js';

@Injectable()
Expand Down Expand Up @@ -172,6 +172,32 @@ export class ClipService {
this.notesRepository.decrement({ id: noteId }, 'clippedCount', 1);
}
@bindThis
async showRemoteOrDummy(clipId: string, author: MiUser|null) : Promise<Packed<'Clip'>> {
if (author == null) {
throw new Error();
}
try {
if (author.host == null) {
throw new Error();
}
return await this.showRemote(clipId, author.host);
} catch {
return await awaitAll({
id: clipId + '@' + (author.host ? author.host : ''),
createdAt: new Date(0).toISOString(),
lastClippedAt: new Date(0).toISOString(),
userId: author.id,
user: this.userEntityService.pack(author),
name: 'Unavailable',
description: '',
isPublic: true,
favoritedCount: 0,
isFavorited: false,
notesCount: 0,
});
}
}
@bindThis
public async showRemote(
clipId:string,
host:string,
Expand Down
129 changes: 129 additions & 0 deletions packages/backend/src/misc/remote-api-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project, yojo-art team
* SPDX-License-Identifier: AGPL-3.0-only
*/

import Redis from 'ioredis';
import got, * as Got from 'got';
import type { Config } from '@/config.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { MiUser } from '@/models/User.js';

export type FetchRemoteApiOpts={
/** リモートで割り当てられているid */
userId?:string,
limit?:number,
sinceId?:string,
untilId?:string,
};

export async function fetch_remote_api(
config: Config, httpRequestService: HttpRequestService, host: string, endpoint: string, opts: FetchRemoteApiOpts,
) {
const url = 'https://' + host + endpoint;
const sinceIdRemote = opts.sinceId ? opts.sinceId.split('@')[0] : undefined;
const untilIdRemote = opts.untilId ? opts.untilId.split('@')[0] : undefined;
const timeout = 30 * 1000;
const operationTimeout = 60 * 1000;
const res = got.post(url, {
headers: {
'User-Agent': config.userAgent,
'Content-Type': 'application/json; charset=utf-8',
},
timeout: {
lookup: timeout,
connect: timeout,
secureConnect: timeout,
socket: timeout, // read timeout
response: timeout,
send: timeout,
request: operationTimeout, // whole operation timeout
},
agent: {
http: httpRequestService.httpAgent,
https: httpRequestService.httpsAgent,
},
http2: true,
retry: {
limit: 1,
},
enableUnixSockets: false,
body: JSON.stringify({
userId: opts.userId,
limit: opts.limit,
sinceId: sinceIdRemote,
untilId: untilIdRemote,
}),
});
return await res.text();
}
/** userがリモートで割り当てられているidを取得 */
export async function fetch_remote_user_id(
config:Config,
httpRequestService: HttpRequestService,
redisForRemoteApis: Redis.Redis,
user:MiUser,
) {
//ローカルのIDからリモートのIDを割り出す
const cache_key = 'remote-userId:' + user.id;
const id = await redisForRemoteApis.get(cache_key);
if (id !== null) {
if (id === '__NOT_MISSKEY') {
return null;
}
if (id === '__INTERVAL') {
return null;
}
//アクセス時に有効期限を更新
redisForRemoteApis.expire(cache_key, 7 * 24 * 60 * 60);
return id;
}
try {
const url = 'https://' + user.host + '/api/users/show';
const timeout = 30 * 1000;
const operationTimeout = 60 * 1000;
const res = got.post(url, {
headers: {
'User-Agent': config.userAgent,
'Content-Type': 'application/json; charset=utf-8',
},
timeout: {
lookup: timeout,
connect: timeout,
secureConnect: timeout,
socket: timeout, // read timeout
response: timeout,
send: timeout,
request: operationTimeout, // whole operation timeout
},
agent: {
http: httpRequestService.httpAgent,
https: httpRequestService.httpsAgent,
},
http2: true,
retry: {
limit: 1,
},
enableUnixSockets: false,
body: JSON.stringify({
username: user.username,
}),
});
const text = await res.text();
const json = JSON.parse(text);
if (json.id != null) {
const redisPipeline = redisForRemoteApis.pipeline();
redisPipeline.set(cache_key, json.id);
//キャッシュ期限1週間
redisPipeline.expire(cache_key, 7 * 24 * 60 * 60);
await redisPipeline.exec();
return json.id as string;
}
} catch {
const redisPipeline = redisForRemoteApis.pipeline();
redisPipeline.set(cache_key, '__INTERVAL');
redisPipeline.expire(cache_key, 60 * 60);
await redisPipeline.exec();
}
return null;
}
8 changes: 8 additions & 0 deletions packages/backend/src/models/ClipFavoriteRemote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ export class MiClipFavoriteRemote {
@JoinColumn()
public user: MiUser | null;

@Column(id())
public authorId: MiUser['id'];
@ManyToOne(type => MiUser, {
onDelete: 'CASCADE',
})
@JoinColumn()
public author: MiUser | null;

@Column('varchar', {
length: 32,
})
Expand Down
3 changes: 2 additions & 1 deletion packages/backend/src/server/api/endpoints/clips/favorite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const host = clipIdArray.length > 1 ? clipIdArray[1] : null;
if (host) {
const clipId = clipIdArray[0];
await clipService.showRemote(clipId, host);
const clip = await clipService.showRemote(clipId, host);

const exist = await this.clipFavoritesRemoteRepository.exists({
where: {
Expand All @@ -94,6 +94,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
clipId: clipId,
host: host,
userId: me.id,
authorId: clip.userId,
});
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
if (ps.withRemote) {
const query = this.clipFavoritesRemoteRepository.createQueryBuilder('favorite')
.andWhere('favorite.userId = :meId', { meId: me.id });
.andWhere('favorite.userId = :meId', { meId: me.id })
.leftJoinAndSelect('favorite.author', 'author');

const favorites = await query.getMany();
const remoteFavorites = await Promise.all(favorites.map(e => clipService.showRemote(e.clipId, e.host)));
let remoteFavorites = await Promise.all(favorites.map(e => clipService.showRemoteOrDummy(e.clipId, e.author)));
remoteFavorites = remoteFavorites.map(clip => {
clip.isFavorited = true;
return clip;
});
myFavorites = myFavorites.concat(remoteFavorites);
}
return myFavorites.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
Expand Down
Loading

0 comments on commit de66bbf

Please sign in to comment.