mirror of
https://iceshrimp.dev/limepotato/jormungandr-bite.git
synced 2024-11-13 13:37:31 -07:00
ページにいいねできるように
This commit is contained in:
parent
d671901d8d
commit
ddeb98ece4
18 changed files with 489 additions and 191 deletions
|
@ -1874,6 +1874,10 @@ pages:
|
|||
edit-this-page: "このページを編集"
|
||||
view-source: "ソースを表示"
|
||||
view-page: "ページを見る"
|
||||
like: "いいね"
|
||||
unlike: "いいね解除"
|
||||
liked-pages: "いいねしたページ"
|
||||
my-pages: "自分のページ"
|
||||
inspector: "インスペクター"
|
||||
content: "ページブロック"
|
||||
variables: "変数"
|
||||
|
|
23
migration/1558072954435-PageLike.ts
Normal file
23
migration/1558072954435-PageLike.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class PageLike1558072954435 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(`CREATE TABLE "page_like" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "pageId" character varying(32) NOT NULL, CONSTRAINT "PK_813f034843af992d3ae0f43c64c" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_0e61efab7f88dbb79c9166dbb4" ON "page_like" ("userId") `);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_4ce6fb9c70529b4c8ac46c9bfa" ON "page_like" ("userId", "pageId") `);
|
||||
await queryRunner.query(`ALTER TABLE "page" ADD "likedCount" integer NOT NULL DEFAULT 0`);
|
||||
await queryRunner.query(`ALTER TABLE "page_like" ADD CONSTRAINT "FK_0e61efab7f88dbb79c9166dbb48" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "page_like" ADD CONSTRAINT "FK_cf8782626dced3176038176a847" FOREIGN KEY ("pageId") REFERENCES "page"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(`ALTER TABLE "page_like" DROP CONSTRAINT "FK_cf8782626dced3176038176a847"`);
|
||||
await queryRunner.query(`ALTER TABLE "page_like" DROP CONSTRAINT "FK_0e61efab7f88dbb79c9166dbb48"`);
|
||||
await queryRunner.query(`ALTER TABLE "page" DROP COLUMN "likedCount"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_4ce6fb9c70529b4c8ac46c9bfa"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_0e61efab7f88dbb79c9166dbb4"`);
|
||||
await queryRunner.query(`DROP TABLE "page_like"`);
|
||||
}
|
||||
|
||||
}
|
|
@ -12,6 +12,11 @@
|
|||
<small>@{{ page.user.username }}</small>
|
||||
<router-link v-if="$store.getters.isSignedIn && $store.state.i.id === page.userId" :to="`/i/pages/edit/${page.id}`">{{ $t('edit-this-page') }}</router-link>
|
||||
<router-link :to="`./${page.name}/view-source`">{{ $t('view-source') }}</router-link>
|
||||
<div class="like">
|
||||
<button @click="unlike()" v-if="page.isLiked" :title="$t('unlike')"><fa :icon="faHeartS"/></button>
|
||||
<button @click="like()" v-else :title="$t('like')"><fa :icon="faHeart"/></button>
|
||||
<span class="count" v-if="page.likedCount > 0">{{ page.likedCount }}</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -19,8 +24,8 @@
|
|||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../../i18n';
|
||||
import { faICursor, faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faSave, faStickyNote } from '@fortawesome/free-regular-svg-icons';
|
||||
import { faHeart as faHeartS } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faHeart } from '@fortawesome/free-regular-svg-icons';
|
||||
import XBlock from './page.block.vue';
|
||||
import { ASEvaluator } from '../../../../../../misc/aiscript/evaluator';
|
||||
import { collectPageVars } from '../../../scripts/collect-page-vars';
|
||||
|
@ -76,7 +81,7 @@ export default Vue.extend({
|
|||
return {
|
||||
page: null,
|
||||
script: null,
|
||||
faPlus, faICursor, faSave, faStickyNote
|
||||
faHeartS, faHeart
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -103,6 +108,24 @@ export default Vue.extend({
|
|||
getPageVars() {
|
||||
return collectPageVars(this.page.content);
|
||||
},
|
||||
|
||||
like() {
|
||||
this.$root.api('pages/like', {
|
||||
pageId: this.page.id,
|
||||
}).then(() => {
|
||||
this.page.isLiked = true;
|
||||
this.page.likedCount++;
|
||||
});
|
||||
},
|
||||
|
||||
unlike() {
|
||||
this.$root.api('pages/unlike', {
|
||||
pageId: this.page.id,
|
||||
}).then(() => {
|
||||
this.page.isLiked = false;
|
||||
this.page.likedCount--;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -161,4 +184,7 @@ export default Vue.extend({
|
|||
> a + a
|
||||
margin-left 8px
|
||||
|
||||
> .like
|
||||
margin-top 16px
|
||||
|
||||
</style>
|
||||
|
|
138
src/client/app/common/views/pages/pages.vue
Normal file
138
src/client/app/common/views/pages/pages.vue
Normal file
|
@ -0,0 +1,138 @@
|
|||
<template>
|
||||
<div>
|
||||
<ui-container :body-togglable="true">
|
||||
<template #header><fa :icon="faEdit" fixed-width/>{{ $t('my-pages') }}</template>
|
||||
<div class="rknalgpo" v-if="!fetching">
|
||||
<ui-button class="new" @click="create()"><fa :icon="faPlus"/></ui-button>
|
||||
<sequential-entrance animation="entranceFromTop" delay="25" tag="div" class="pages">
|
||||
<x-page-preview v-for="page in pages" class="page" :page="page" :key="page.id"/>
|
||||
</sequential-entrance>
|
||||
<ui-button v-if="existMore" @click="fetchMore()">{{ $t('@.load-more') }}</ui-button>
|
||||
</div>
|
||||
</ui-container>
|
||||
|
||||
<ui-container :body-togglable="true">
|
||||
<template #header><fa :icon="faHeart" fixed-width/>{{ $t('liked-pages') }}</template>
|
||||
<div class="rknalgpo" v-if="!fetching">
|
||||
<sequential-entrance animation="entranceFromTop" delay="25" tag="div" class="pages">
|
||||
<x-page-preview v-for="like in likes" class="page" :page="like.page" :key="like.page.id"/>
|
||||
</sequential-entrance>
|
||||
<ui-button v-if="existMoreLikes" @click="fetchMoreLiked()">{{ $t('@.load-more') }}</ui-button>
|
||||
</div>
|
||||
</ui-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faPlus, faEdit } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faStickyNote, faHeart } from '@fortawesome/free-regular-svg-icons';
|
||||
import i18n from '../../../i18n';
|
||||
import Progress from '../../scripts/loading';
|
||||
import XPagePreview from '../../views/components/page-preview.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('pages'),
|
||||
components: {
|
||||
XPagePreview
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
pages: [],
|
||||
existMore: false,
|
||||
moreFetching: false,
|
||||
likes: [],
|
||||
existMoreLikes: false,
|
||||
moreLikesFetching: false,
|
||||
faStickyNote, faPlus, faEdit, faHeart
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.fetch();
|
||||
},
|
||||
methods: {
|
||||
async fetch() {
|
||||
Progress.start();
|
||||
this.fetching = true;
|
||||
|
||||
const pages = await this.$root.api('i/pages', {
|
||||
limit: 11
|
||||
});
|
||||
|
||||
if (pages.length == 11) {
|
||||
this.existMore = true;
|
||||
pages.pop();
|
||||
}
|
||||
|
||||
const likes = await this.$root.api('i/page-likes', {
|
||||
limit: 11
|
||||
});
|
||||
|
||||
if (likes.length == 11) {
|
||||
this.existMoreLikes = true;
|
||||
likes.pop();
|
||||
}
|
||||
|
||||
this.pages = pages;
|
||||
this.likes = likes;
|
||||
this.fetching = false;
|
||||
|
||||
Progress.done();
|
||||
},
|
||||
fetchMore() {
|
||||
this.moreFetching = true;
|
||||
this.$root.api('i/pages', {
|
||||
limit: 11,
|
||||
untilId: this.pages[this.pages.length - 1].id
|
||||
}).then(pages => {
|
||||
if (pages.length == 11) {
|
||||
this.existMore = true;
|
||||
pages.pop();
|
||||
} else {
|
||||
this.existMore = false;
|
||||
}
|
||||
|
||||
this.pages = this.pages.concat(pages);
|
||||
this.moreFetching = false;
|
||||
});
|
||||
},
|
||||
fetchMoreLiked() {
|
||||
this.moreLikesFetching = true;
|
||||
this.$root.api('i/page-likes', {
|
||||
limit: 11,
|
||||
untilId: this.likes[this.likes.length - 1].id
|
||||
}).then(pages => {
|
||||
if (pages.length == 11) {
|
||||
this.existMoreLikes = true;
|
||||
pages.pop();
|
||||
} else {
|
||||
this.existMoreLikes = false;
|
||||
}
|
||||
|
||||
this.likes = this.likes.concat(pages);
|
||||
this.moreLikesFetching = false;
|
||||
});
|
||||
},
|
||||
create() {
|
||||
this.$router.push(`/i/pages/new`);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.rknalgpo
|
||||
padding 16px
|
||||
|
||||
> .new
|
||||
margin-bottom 16px
|
||||
|
||||
> * > .page
|
||||
margin-bottom 8px
|
||||
|
||||
@media (min-width 500px)
|
||||
> * > .page
|
||||
margin-bottom 16px
|
||||
|
||||
</style>
|
|
@ -156,7 +156,7 @@ init(async (launch, os) => {
|
|||
{ path: '/explore', name: 'explore', component: () => import('../common/views/pages/explore.vue').then(m => m.default) },
|
||||
{ path: '/explore/tags/:tag', name: 'explore-tag', props: true, component: () => import('../common/views/pages/explore.vue').then(m => m.default) },
|
||||
{ path: '/i/favorites', component: () => import('./views/home/favorites.vue').then(m => m.default) },
|
||||
{ path: '/i/pages', component: () => import('./views/home/pages.vue').then(m => m.default) },
|
||||
{ path: '/i/pages', component: () => import('../common/views/pages/pages.vue').then(m => m.default) },
|
||||
]},
|
||||
{ path: '/@:user/pages/:page', props: true, component: () => import('./views/pages/page.vue').then(m => m.default) },
|
||||
{ path: '/@:user/pages/:pageName/view-source', props: true, component: () => import('./views/pages/page-editor.vue').then(m => m.default) },
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
<template>
|
||||
<div class="rknalgpo" v-if="!fetching">
|
||||
<ui-button @click="create()"><fa :icon="faPlus"/></ui-button>
|
||||
<sequential-entrance animation="entranceFromTop" delay="25">
|
||||
<template v-for="page in pages">
|
||||
<x-page-preview class="page" :page="page" :key="page.id"/>
|
||||
</template>
|
||||
</sequential-entrance>
|
||||
<ui-button v-if="existMore" @click="fetchMore()">{{ $t('@.load-more') }}</ui-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import Progress from '../../../common/scripts/loading';
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faStickyNote } from '@fortawesome/free-regular-svg-icons';
|
||||
import XPagePreview from '../../../common/views/components/page-preview.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n(),
|
||||
components: {
|
||||
XPagePreview
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
pages: [],
|
||||
existMore: false,
|
||||
moreFetching: false,
|
||||
faStickyNote, faPlus
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.fetch();
|
||||
},
|
||||
methods: {
|
||||
fetch() {
|
||||
Progress.start();
|
||||
this.fetching = true;
|
||||
|
||||
this.$root.api('i/pages', {
|
||||
limit: 11
|
||||
}).then(pages => {
|
||||
if (pages.length == 11) {
|
||||
this.existMore = true;
|
||||
pages.pop();
|
||||
}
|
||||
|
||||
this.pages = pages;
|
||||
this.fetching = false;
|
||||
|
||||
Progress.done();
|
||||
});
|
||||
},
|
||||
fetchMore() {
|
||||
this.moreFetching = true;
|
||||
this.$root.api('i/pages', {
|
||||
limit: 11,
|
||||
untilId: this.pages[this.pages.length - 1].id
|
||||
}).then(pages => {
|
||||
if (pages.length == 11) {
|
||||
this.existMore = true;
|
||||
pages.pop();
|
||||
} else {
|
||||
this.existMore = false;
|
||||
}
|
||||
|
||||
this.pages = this.pages.concat(pages);
|
||||
this.moreFetching = false;
|
||||
});
|
||||
},
|
||||
create() {
|
||||
this.$router.push(`/i/pages/new`);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.rknalgpo
|
||||
margin 0 auto
|
||||
|
||||
> * > .page
|
||||
margin-bottom 8px
|
||||
|
||||
@media (min-width 500px)
|
||||
> * > .page
|
||||
margin-bottom 16px
|
||||
|
||||
</style>
|
|
@ -3,92 +3,27 @@
|
|||
<template #header><span style="margin-right:4px;"><fa :icon="faStickyNote"/></span>{{ $t('@.pages') }}</template>
|
||||
|
||||
<main>
|
||||
<ui-button @click="create()"><fa :icon="faPlus"/></ui-button>
|
||||
<sequential-entrance animation="entranceFromTop" delay="25">
|
||||
<template v-for="page in pages">
|
||||
<x-page-preview class="page" :page="page" :key="page.id"/>
|
||||
</template>
|
||||
</sequential-entrance>
|
||||
<ui-button v-if="existMore" @click="fetchMore()">{{ $t('@.load-more') }}</ui-button>
|
||||
<x-pages v-bind="$attrs"/>
|
||||
</main>
|
||||
</mk-ui>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
import Progress from '../../../common/scripts/loading';
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faStickyNote } from '@fortawesome/free-regular-svg-icons';
|
||||
import XPagePreview from '../../../common/views/components/page-preview.vue';
|
||||
import { faHashtag } from '@fortawesome/free-solid-svg-icons';
|
||||
import XPages from '../../../common/views/pages/pages.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n(),
|
||||
i18n: i18n(''),
|
||||
components: {
|
||||
XPagePreview
|
||||
XPages
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
pages: [],
|
||||
existMore: false,
|
||||
moreFetching: false,
|
||||
faStickyNote, faPlus
|
||||
faHashtag
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.fetch();
|
||||
},
|
||||
methods: {
|
||||
fetch() {
|
||||
Progress.start();
|
||||
this.fetching = true;
|
||||
|
||||
this.$root.api('i/pages', {
|
||||
limit: 11
|
||||
}).then(pages => {
|
||||
if (pages.length == 11) {
|
||||
this.existMore = true;
|
||||
pages.pop();
|
||||
}
|
||||
|
||||
this.pages = pages;
|
||||
this.fetching = false;
|
||||
|
||||
Progress.done();
|
||||
});
|
||||
},
|
||||
fetchMore() {
|
||||
this.moreFetching = true;
|
||||
this.$root.api('i/pages', {
|
||||
limit: 11,
|
||||
untilId: this.pages[this.pages.length - 1].id
|
||||
}).then(pages => {
|
||||
if (pages.length == 11) {
|
||||
this.existMore = true;
|
||||
pages.pop();
|
||||
} else {
|
||||
this.existMore = false;
|
||||
}
|
||||
|
||||
this.pages = this.pages.concat(pages);
|
||||
this.moreFetching = false;
|
||||
});
|
||||
},
|
||||
create() {
|
||||
this.$router.push(`/i/pages/new`);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
main
|
||||
> * > .page
|
||||
margin-bottom 8px
|
||||
|
||||
@media (min-width 500px)
|
||||
> * > .page
|
||||
margin-bottom 16px
|
||||
|
||||
</style>
|
||||
|
|
|
@ -41,6 +41,7 @@ import { UserKeypair } from '../models/entities/user-keypair';
|
|||
import { UserPublickey } from '../models/entities/user-publickey';
|
||||
import { UserProfile } from '../models/entities/user-profile';
|
||||
import { Page } from '../models/entities/page';
|
||||
import { PageLike } from '../models/entities/page-like';
|
||||
|
||||
const sqlLogger = dbLogger.createSubLogger('sql', 'white', false);
|
||||
|
||||
|
@ -116,6 +117,7 @@ export function initDb(justBorrow = false, sync = false, log = false) {
|
|||
NoteWatching,
|
||||
NoteUnread,
|
||||
Page,
|
||||
PageLike,
|
||||
Log,
|
||||
DriveFile,
|
||||
DriveFolder,
|
||||
|
|
33
src/models/entities/page-like.ts
Normal file
33
src/models/entities/page-like.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { id } from '../id';
|
||||
import { Page } from './page';
|
||||
|
||||
@Entity()
|
||||
@Index(['userId', 'pageId'], { unique: true })
|
||||
export class PageLike {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone')
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Column(id())
|
||||
public pageId: Page['id'];
|
||||
|
||||
@ManyToOne(type => Page, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public page: Page | null;
|
||||
}
|
|
@ -95,6 +95,11 @@ export class Page {
|
|||
})
|
||||
public visibleUserIds: User['id'][];
|
||||
|
||||
@Column('integer', {
|
||||
default: 0
|
||||
})
|
||||
public likedCount: number;
|
||||
|
||||
constructor(data: Partial<Page>) {
|
||||
if (data == null) return;
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ import { AuthSessionRepository } from './repositories/auth-session';
|
|||
import { UserProfile } from './entities/user-profile';
|
||||
import { HashtagRepository } from './repositories/hashtag';
|
||||
import { PageRepository } from './repositories/page';
|
||||
import { PageLikeRepository } from './repositories/page-like';
|
||||
|
||||
export const Apps = getCustomRepository(AppRepository);
|
||||
export const Notes = getCustomRepository(NoteRepository);
|
||||
|
@ -74,3 +75,4 @@ export const ReversiGames = getCustomRepository(ReversiGameRepository);
|
|||
export const ReversiMatchings = getCustomRepository(ReversiMatchingRepository);
|
||||
export const Logs = getRepository(Log);
|
||||
export const Pages = getCustomRepository(PageRepository);
|
||||
export const PageLikes = getCustomRepository(PageLikeRepository);
|
||||
|
|
26
src/models/repositories/page-like.ts
Normal file
26
src/models/repositories/page-like.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { EntityRepository, Repository } from 'typeorm';
|
||||
import { PageLike } from '../entities/page-like';
|
||||
import { Pages } from '..';
|
||||
import { ensure } from '../../prelude/ensure';
|
||||
|
||||
@EntityRepository(PageLike)
|
||||
export class PageLikeRepository extends Repository<PageLike> {
|
||||
public async pack(
|
||||
src: PageLike['id'] | PageLike,
|
||||
me?: any
|
||||
) {
|
||||
const like = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||
|
||||
return {
|
||||
id: like.id,
|
||||
page: await Pages.pack(like.page || like.pageId, me),
|
||||
};
|
||||
}
|
||||
|
||||
public packMany(
|
||||
likes: any[],
|
||||
me: any
|
||||
) {
|
||||
return Promise.all(likes.map(x => this.pack(x, me)));
|
||||
}
|
||||
}
|
|
@ -1,24 +1,30 @@
|
|||
import { EntityRepository, Repository } from 'typeorm';
|
||||
import { Page } from '../entities/page';
|
||||
import { SchemaType, types, bool } from '../../misc/schema';
|
||||
import { Users, DriveFiles } from '..';
|
||||
import { Users, DriveFiles, PageLikes } from '..';
|
||||
import { awaitAll } from '../../prelude/await-all';
|
||||
import { DriveFile } from '../entities/drive-file';
|
||||
import { User } from '../entities/user';
|
||||
import { ensure } from '../../prelude/ensure';
|
||||
|
||||
export type PackedPage = SchemaType<typeof packedPageSchema>;
|
||||
|
||||
@EntityRepository(Page)
|
||||
export class PageRepository extends Repository<Page> {
|
||||
public async pack(
|
||||
src: Page,
|
||||
src: Page['id'] | Page,
|
||||
me?: User['id'] | User | null | undefined,
|
||||
): Promise<PackedPage> {
|
||||
const meId = me ? typeof me === 'string' ? me : me.id : null;
|
||||
const page = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
|
||||
|
||||
const attachedFiles: Promise<DriveFile | undefined>[] = [];
|
||||
const collectFile = (xs: any[]) => {
|
||||
for (const x of xs) {
|
||||
if (x.type === 'image') {
|
||||
attachedFiles.push(DriveFiles.findOne({
|
||||
id: x.fileId,
|
||||
userId: src.userId
|
||||
userId: page.userId
|
||||
}));
|
||||
}
|
||||
if (x.children) {
|
||||
|
@ -26,7 +32,7 @@ export class PageRepository extends Repository<Page> {
|
|||
}
|
||||
}
|
||||
};
|
||||
collectFile(src.content);
|
||||
collectFile(page.content);
|
||||
|
||||
// 後方互換性のため
|
||||
let migrated = false;
|
||||
|
@ -47,29 +53,31 @@ export class PageRepository extends Repository<Page> {
|
|||
}
|
||||
}
|
||||
};
|
||||
migrate(src.content);
|
||||
migrate(page.content);
|
||||
if (migrated) {
|
||||
this.update(src.id, {
|
||||
content: src.content
|
||||
this.update(page.id, {
|
||||
content: page.content
|
||||
});
|
||||
}
|
||||
|
||||
return await awaitAll({
|
||||
id: src.id,
|
||||
createdAt: src.createdAt.toISOString(),
|
||||
updatedAt: src.updatedAt.toISOString(),
|
||||
userId: src.userId,
|
||||
user: Users.pack(src.user || src.userId),
|
||||
content: src.content,
|
||||
variables: src.variables,
|
||||
title: src.title,
|
||||
name: src.name,
|
||||
summary: src.summary,
|
||||
alignCenter: src.alignCenter,
|
||||
font: src.font,
|
||||
eyeCatchingImageId: src.eyeCatchingImageId,
|
||||
eyeCatchingImage: src.eyeCatchingImageId ? await DriveFiles.pack(src.eyeCatchingImageId) : null,
|
||||
attachedFiles: DriveFiles.packMany(await Promise.all(attachedFiles))
|
||||
id: page.id,
|
||||
createdAt: page.createdAt.toISOString(),
|
||||
updatedAt: page.updatedAt.toISOString(),
|
||||
userId: page.userId,
|
||||
user: Users.pack(page.user || page.userId),
|
||||
content: page.content,
|
||||
variables: page.variables,
|
||||
title: page.title,
|
||||
name: page.name,
|
||||
summary: page.summary,
|
||||
alignCenter: page.alignCenter,
|
||||
font: page.font,
|
||||
eyeCatchingImageId: page.eyeCatchingImageId,
|
||||
eyeCatchingImage: page.eyeCatchingImageId ? await DriveFiles.pack(page.eyeCatchingImageId) : null,
|
||||
attachedFiles: DriveFiles.packMany(await Promise.all(attachedFiles)),
|
||||
likedCount: page.likedCount,
|
||||
isLiked: meId ? await PageLikes.findOne({ pageId: page.id, userId: meId }).then(x => x != null) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
45
src/server/api/endpoints/i/page-likes.ts
Normal file
45
src/server/api/endpoints/i/page-likes.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
import $ from 'cafy';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { PageLikes } from '../../../../models';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': '「いいね」したページ一覧を取得します。',
|
||||
'en-US': 'Get liked pages'
|
||||
},
|
||||
|
||||
tags: ['account', 'pages'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:page-likes',
|
||||
|
||||
params: {
|
||||
limit: {
|
||||
validator: $.optional.num.range(1, 100),
|
||||
default: 10
|
||||
},
|
||||
|
||||
sinceId: {
|
||||
validator: $.optional.type(ID),
|
||||
},
|
||||
|
||||
untilId: {
|
||||
validator: $.optional.type(ID),
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
const query = makePaginationQuery(PageLikes.createQueryBuilder('like'), ps.sinceId, ps.untilId)
|
||||
.andWhere(`like.userId = :meId`, { meId: user.id })
|
||||
.leftJoinAndSelect('like.page', 'page');
|
||||
|
||||
const likes = await query
|
||||
.take(ps.limit!)
|
||||
.getMany();
|
||||
|
||||
return await PageLikes.packMany(likes, user);
|
||||
});
|
79
src/server/api/endpoints/pages/like.ts
Normal file
79
src/server/api/endpoints/pages/like.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
import $ from 'cafy';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { ApiError } from '../../error';
|
||||
import { Pages, PageLikes } from '../../../../models';
|
||||
import { genId } from '../../../../misc/gen-id';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': '指定したページを「いいね」します。',
|
||||
},
|
||||
|
||||
tags: ['pages'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:page-likes',
|
||||
|
||||
params: {
|
||||
pageId: {
|
||||
validator: $.type(ID),
|
||||
desc: {
|
||||
'ja-JP': '対象のページのID',
|
||||
'en-US': 'Target page ID.'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchPage: {
|
||||
message: 'No such page.',
|
||||
code: 'NO_SUCH_PAGE',
|
||||
id: 'cc98a8a2-0dc3-4123-b198-62c71df18ed3'
|
||||
},
|
||||
|
||||
yourPage: {
|
||||
message: 'You cannot like your page.',
|
||||
code: 'YOUR_PAGE',
|
||||
id: '28800466-e6db-40f2-8fae-bf9e82aa92b8'
|
||||
},
|
||||
|
||||
alreadyLiked: {
|
||||
message: 'The page has already been liked.',
|
||||
code: 'ALREADY_LIKED',
|
||||
id: 'cc98a8a2-0dc3-4123-b198-62c71df18ed3'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
const page = await Pages.findOne(ps.pageId);
|
||||
if (page == null) {
|
||||
throw new ApiError(meta.errors.noSuchPage);
|
||||
}
|
||||
|
||||
if (page.userId === user.id) {
|
||||
throw new ApiError(meta.errors.yourPage);
|
||||
}
|
||||
|
||||
// if already liked
|
||||
const exist = await PageLikes.findOne({
|
||||
pageId: page.id,
|
||||
userId: user.id
|
||||
});
|
||||
|
||||
if (exist != null) {
|
||||
throw new ApiError(meta.errors.alreadyLiked);
|
||||
}
|
||||
|
||||
// Create like
|
||||
await PageLikes.save({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
pageId: page.id,
|
||||
userId: user.id
|
||||
});
|
||||
|
||||
Pages.increment({ id: page.id }, 'likedCount', 1);
|
||||
});
|
|
@ -70,5 +70,5 @@ export default define(meta, async (ps, user) => {
|
|||
throw new ApiError(meta.errors.noSuchPage);
|
||||
}
|
||||
|
||||
return await Pages.pack(page);
|
||||
return await Pages.pack(page, user);
|
||||
});
|
||||
|
|
62
src/server/api/endpoints/pages/unlike.ts
Normal file
62
src/server/api/endpoints/pages/unlike.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
import $ from 'cafy';
|
||||
import { ID } from '../../../../misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { ApiError } from '../../error';
|
||||
import { Pages, PageLikes } from '../../../../models';
|
||||
|
||||
export const meta = {
|
||||
desc: {
|
||||
'ja-JP': '指定したページの「いいね」を解除します。',
|
||||
},
|
||||
|
||||
tags: ['pages'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:page-likes',
|
||||
|
||||
params: {
|
||||
pageId: {
|
||||
validator: $.type(ID),
|
||||
desc: {
|
||||
'ja-JP': '対象のページのID',
|
||||
'en-US': 'Target page ID.'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchPage: {
|
||||
message: 'No such page.',
|
||||
code: 'NO_SUCH_PAGE',
|
||||
id: 'a0d41e20-1993-40bd-890e-f6e560ae648e'
|
||||
},
|
||||
|
||||
notLiked: {
|
||||
message: 'You have not liked that page.',
|
||||
code: 'NOT_LIKED',
|
||||
id: 'f5e586b0-ce93-4050-b0e3-7f31af5259ee'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
const page = await Pages.findOne(ps.pageId);
|
||||
if (page == null) {
|
||||
throw new ApiError(meta.errors.noSuchPage);
|
||||
}
|
||||
|
||||
const exist = await PageLikes.findOne({
|
||||
pageId: page.id,
|
||||
userId: user.id
|
||||
});
|
||||
|
||||
if (exist == null) {
|
||||
throw new ApiError(meta.errors.notLiked);
|
||||
}
|
||||
|
||||
// Delete like
|
||||
await PageLikes.delete(exist.id);
|
||||
|
||||
Pages.decrement({ id: page.id }, 'likedCount', 1);
|
||||
});
|
|
@ -21,4 +21,6 @@ export const kinds = [
|
|||
'write:votes',
|
||||
'read:pages',
|
||||
'write:pages',
|
||||
'write:page-likes',
|
||||
'read:page-likes',
|
||||
];
|
||||
|
|
Loading…
Reference in a new issue