enhance(backend): 個人宛のお知らせはわかったを押すと過去のお知らせに表示させる (MisskeyIO#736)
This commit is contained in:
parent
46e4b8f8e1
commit
8e9b6dc4d1
@ -138,7 +138,7 @@ export class AnnouncementService {
|
|||||||
limit: number,
|
limit: number,
|
||||||
offset: number,
|
offset: number,
|
||||||
moderator: MiUser,
|
moderator: MiUser,
|
||||||
): Promise<(MiAnnouncement & { userInfo: Packed<'UserLite'> | null, reads: number })[]> {
|
): Promise<(MiAnnouncement & { userInfo: Packed<'UserLite'> | null, reads: number, lastReadAt: Date | null })[]> {
|
||||||
const query = this.announcementsRepository.createQueryBuilder('announcement');
|
const query = this.announcementsRepository.createQueryBuilder('announcement');
|
||||||
|
|
||||||
if (userId) {
|
if (userId) {
|
||||||
@ -157,13 +157,14 @@ export class AnnouncementService {
|
|||||||
.offset(offset)
|
.offset(offset)
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
const reads = new Map<MiAnnouncement, number>();
|
const reads = announcements.length > 0
|
||||||
|
? await this.announcementReadsRepository.createQueryBuilder()
|
||||||
for (const announcement of announcements) {
|
.select('"announcementId", count(*) as "reads", max("id") as "lastReadId"')
|
||||||
reads.set(announcement, await this.announcementReadsRepository.countBy({
|
.where('"announcementId" IN (:...announcementIds)', { announcementIds: announcements.map(a => a.id) })
|
||||||
announcementId: announcement.id,
|
.groupBy('"announcementId"')
|
||||||
}));
|
.getRawMany<{ announcementId: string, reads: number, lastReadId: string | null }>()
|
||||||
}
|
.then(rs => new Map(rs.map(r => [r.announcementId, { reads: r.reads, lastReadAt: r.lastReadId ? this.idService.parse(r.lastReadId).date : null }])))
|
||||||
|
: new Map();
|
||||||
|
|
||||||
const users = await this.usersRepository.findBy({
|
const users = await this.usersRepository.findBy({
|
||||||
id: In(announcements.map(a => a.userId).filter(id => id != null)),
|
id: In(announcements.map(a => a.userId).filter(id => id != null)),
|
||||||
@ -174,8 +175,8 @@ export class AnnouncementService {
|
|||||||
|
|
||||||
return announcements.map(announcement => ({
|
return announcements.map(announcement => ({
|
||||||
...announcement,
|
...announcement,
|
||||||
|
...reads.get(announcement.id) ?? { reads: 0, lastReadAt: null },
|
||||||
userInfo: packedUsers.find(u => u.id === announcement.userId) ?? null,
|
userInfo: packedUsers.find(u => u.id === announcement.userId) ?? null,
|
||||||
reads: reads.get(announcement) ?? 0,
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,18 +294,20 @@ export class AnnouncementService {
|
|||||||
'read.id IS NOT NULL as "isRead"',
|
'read.id IS NOT NULL as "isRead"',
|
||||||
]);
|
]);
|
||||||
query
|
query
|
||||||
.andWhere(
|
.andWhere(new Brackets((qb) => {
|
||||||
new Brackets((qb) => {
|
qb.orWhere(new Brackets((nqb) => {
|
||||||
qb.orWhere('announcement."userId" = :userId', { userId: me.id });
|
nqb.andWhere('announcement."userId" = :userId', { userId: me.id });
|
||||||
qb.orWhere('announcement."userId" IS NULL');
|
nqb.andWhere(isActive ? 'read.id IS NULL' : 'read.id IS NOT NULL');
|
||||||
}),
|
}));
|
||||||
)
|
qb.orWhere(new Brackets((nqb) => {
|
||||||
.andWhere(
|
nqb.andWhere('announcement."userId" IS NULL');
|
||||||
new Brackets((qb) => {
|
nqb.andWhere('announcement."isActive" = :isActive', { isActive });
|
||||||
qb.orWhere('announcement."forExistingUsers" = false');
|
}));
|
||||||
qb.orWhere('announcement.id > :userId', { userId: me.id });
|
}))
|
||||||
}),
|
.andWhere(new Brackets((qb) => {
|
||||||
);
|
qb.orWhere('announcement."forExistingUsers" = false');
|
||||||
|
qb.orWhere('announcement.id > :userId', { userId: me.id });
|
||||||
|
}));
|
||||||
} else {
|
} else {
|
||||||
query.select([
|
query.select([
|
||||||
'announcement.*',
|
'announcement.*',
|
||||||
@ -312,12 +315,9 @@ export class AnnouncementService {
|
|||||||
]);
|
]);
|
||||||
query.andWhere('announcement."userId" IS NULL');
|
query.andWhere('announcement."userId" IS NULL');
|
||||||
query.andWhere('announcement."forExistingUsers" = false');
|
query.andWhere('announcement."forExistingUsers" = false');
|
||||||
|
query.andWhere('announcement."isActive" = :isActive', { isActive });
|
||||||
}
|
}
|
||||||
|
|
||||||
query.andWhere('announcement."isActive" = :isActive', {
|
|
||||||
isActive: isActive,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
query.orderBy({
|
query.orderBy({
|
||||||
'"isRead"': 'ASC',
|
'"isRead"': 'ASC',
|
||||||
|
@ -97,6 +97,11 @@ export const meta = {
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
lastReadAt: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: true,
|
||||||
|
format: 'date-time',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -140,6 +145,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
userId: announcement.userId,
|
userId: announcement.userId,
|
||||||
user: announcement.userInfo,
|
user: announcement.userInfo,
|
||||||
reads: announcement.reads,
|
reads: announcement.reads,
|
||||||
|
lastReadAt: announcement.lastReadAt?.toISOString() ?? null,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
{{ i18n.ts._announcement.silence }}
|
{{ i18n.ts._announcement.silence }}
|
||||||
<template #caption>{{ i18n.ts._announcement.silenceDescription }}</template>
|
<template #caption>{{ i18n.ts._announcement.silenceDescription }}</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
<p v-if="reads">{{ i18n.tsx.nUsersRead({ n: reads }) }}</p>
|
<p v-if="reads">{{ i18n.tsx.nUsersRead({ n: reads }) }} <span v-if="lastReadAt">(<MkTime :time="lastReadAt" mode="absolute"/>)</span></p>
|
||||||
<MkUserCardMini v-if="props.user.id" :user="props.user"></MkUserCardMini>
|
<MkUserCardMini v-if="props.user.id" :user="props.user"></MkUserCardMini>
|
||||||
<MkButton v-if="announcement" danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
<MkButton v-if="announcement" danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
@ -94,6 +94,7 @@ const closeDuration = ref<number>(props.announcement ? props.announcement.closeD
|
|||||||
const displayOrder = ref<number>(props.announcement ? props.announcement.displayOrder : 0);
|
const displayOrder = ref<number>(props.announcement ? props.announcement.displayOrder : 0);
|
||||||
const silence = ref<boolean>(props.announcement ? props.announcement.silence : false);
|
const silence = ref<boolean>(props.announcement ? props.announcement.silence : false);
|
||||||
const reads = ref<number>(props.announcement ? props.announcement.reads : 0);
|
const reads = ref<number>(props.announcement ? props.announcement.reads : 0);
|
||||||
|
const lastReadAt = ref<string | null>(props.announcement ? props.announcement.lastReadAt : null);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void,
|
(ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void,
|
||||||
|
@ -149,7 +149,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i>
|
<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i>
|
||||||
</span>
|
</span>
|
||||||
<span>{{ announcement.title }}</span>
|
<span>{{ announcement.title }}</span>
|
||||||
<span v-if="announcement.reads > 0" style="margin-left: auto; opacity: 0.7;">{{ i18n.ts.messageRead }}</span>
|
<span v-if="announcement.reads > 0" style="margin-left: auto; opacity: 0.7;">{{ i18n.ts.messageRead }} <span v-if="announcement.lastReadAt">(<MkTime :time="announcement.lastReadAt" mode="absolute"/>)</span></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -76,7 +76,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkSwitch v-model="announcement.silence" :helpText="i18n.ts._announcement.silenceDescription">
|
<MkSwitch v-model="announcement.silence" :helpText="i18n.ts._announcement.silenceDescription">
|
||||||
{{ i18n.ts._announcement.silence }}
|
{{ i18n.ts._announcement.silence }}
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
<p v-if="announcement.reads">{{ i18n.tsx.nUsersRead({ n: announcement.reads }) }}</p>
|
<p v-if="announcement.reads">{{ i18n.tsx.nUsersRead({ n: announcement.reads }) }} <span v-if="announcement.lastReadAt">(<MkTime :time="announcement.lastReadAt" mode="absolute"/>)</span></p>
|
||||||
<MkUserCardMini v-if="announcement.userId" :user="announcement.user" @click="editUser(announcement)"></MkUserCardMini>
|
<MkUserCardMini v-if="announcement.userId" :user="announcement.user" @click="editUser(announcement)"></MkUserCardMini>
|
||||||
<MkButton v-else class="button" inline primary @click="editUser(announcement)">{{ i18n.ts.specifyUser }}</MkButton>
|
<MkButton v-else class="button" inline primary @click="editUser(announcement)">{{ i18n.ts.specifyUser }}</MkButton>
|
||||||
<div class="buttons _buttons">
|
<div class="buttons _buttons">
|
||||||
|
@ -6183,6 +6183,8 @@ export type operations = {
|
|||||||
userId: string | null;
|
userId: string | null;
|
||||||
user: components['schemas']['UserLite'] | null;
|
user: components['schemas']['UserLite'] | null;
|
||||||
reads: number;
|
reads: number;
|
||||||
|
/** Format: date-time */
|
||||||
|
lastReadAt: string | null;
|
||||||
})[];
|
})[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user