68
packages/backend/src/server/api/endpoints/channels/create.ts
Normal file
68
packages/backend/src/server/api/endpoints/channels/create.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import $ from 'cafy';
|
||||
import define from '../../define';
|
||||
import { ApiError } from '../../error';
|
||||
import { Channels, DriveFiles } from '@/models/index';
|
||||
import { Channel } from '@/models/entities/channel';
|
||||
import { genId } from '@/misc/gen-id';
|
||||
import { ID } from '@/misc/cafy-id';
|
||||
|
||||
export const meta = {
|
||||
tags: ['channels'],
|
||||
|
||||
requireCredential: true as const,
|
||||
|
||||
kind: 'write:channels',
|
||||
|
||||
params: {
|
||||
name: {
|
||||
validator: $.str.range(1, 128)
|
||||
},
|
||||
|
||||
description: {
|
||||
validator: $.nullable.optional.str.range(1, 2048)
|
||||
},
|
||||
|
||||
bannerId: {
|
||||
validator: $.nullable.optional.type(ID),
|
||||
}
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'object' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
ref: 'Channel',
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchFile: {
|
||||
message: 'No such file.',
|
||||
code: 'NO_SUCH_FILE',
|
||||
id: 'cd1e9f3e-5a12-4ab4-96f6-5d0a2cc32050'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
let banner = null;
|
||||
if (ps.bannerId != null) {
|
||||
banner = await DriveFiles.findOne({
|
||||
id: ps.bannerId,
|
||||
userId: user.id
|
||||
});
|
||||
|
||||
if (banner == null) {
|
||||
throw new ApiError(meta.errors.noSuchFile);
|
||||
}
|
||||
}
|
||||
|
||||
const channel = await Channels.save({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
userId: user.id,
|
||||
name: ps.name,
|
||||
description: ps.description || null,
|
||||
bannerId: banner ? banner.id : null,
|
||||
} as Channel);
|
||||
|
||||
return await Channels.pack(channel, user);
|
||||
});
|
@@ -0,0 +1,28 @@
|
||||
import define from '../../define';
|
||||
import { Channels } from '@/models/index';
|
||||
|
||||
export const meta = {
|
||||
tags: ['channels'],
|
||||
|
||||
requireCredential: false as const,
|
||||
|
||||
res: {
|
||||
type: 'array' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
items: {
|
||||
type: 'object' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
ref: 'Channel',
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, me) => {
|
||||
const query = Channels.createQueryBuilder('channel')
|
||||
.where('channel.lastNotedAt IS NOT NULL')
|
||||
.orderBy('channel.lastNotedAt', 'DESC');
|
||||
|
||||
const channels = await query.take(10).getMany();
|
||||
|
||||
return await Promise.all(channels.map(x => Channels.pack(x, me)));
|
||||
});
|
48
packages/backend/src/server/api/endpoints/channels/follow.ts
Normal file
48
packages/backend/src/server/api/endpoints/channels/follow.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import $ from 'cafy';
|
||||
import { ID } from '@/misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { ApiError } from '../../error';
|
||||
import { Channels, ChannelFollowings } from '@/models/index';
|
||||
import { genId } from '@/misc/gen-id';
|
||||
import { publishUserEvent } from '@/services/stream';
|
||||
|
||||
export const meta = {
|
||||
tags: ['channels'],
|
||||
|
||||
requireCredential: true as const,
|
||||
|
||||
kind: 'write:channels',
|
||||
|
||||
params: {
|
||||
channelId: {
|
||||
validator: $.type(ID),
|
||||
},
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchChannel: {
|
||||
message: 'No such channel.',
|
||||
code: 'NO_SUCH_CHANNEL',
|
||||
id: 'c0031718-d573-4e85-928e-10039f1fbb68'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
const channel = await Channels.findOne({
|
||||
id: ps.channelId,
|
||||
});
|
||||
|
||||
if (channel == null) {
|
||||
throw new ApiError(meta.errors.noSuchChannel);
|
||||
}
|
||||
|
||||
await ChannelFollowings.insert({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
followerId: user.id,
|
||||
followeeId: channel.id,
|
||||
});
|
||||
|
||||
publishUserEvent(user.id, 'followChannel', channel);
|
||||
});
|
@@ -0,0 +1,49 @@
|
||||
import $ from 'cafy';
|
||||
import { ID } from '@/misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { Channels, ChannelFollowings } from '@/models/index';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
|
||||
export const meta = {
|
||||
tags: ['channels', 'account'],
|
||||
|
||||
requireCredential: true as const,
|
||||
|
||||
kind: 'read:channels',
|
||||
|
||||
params: {
|
||||
sinceId: {
|
||||
validator: $.optional.type(ID),
|
||||
},
|
||||
|
||||
untilId: {
|
||||
validator: $.optional.type(ID),
|
||||
},
|
||||
|
||||
limit: {
|
||||
validator: $.optional.num.range(1, 100),
|
||||
default: 5
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'array' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
items: {
|
||||
type: 'object' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
ref: 'Channel',
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, me) => {
|
||||
const query = makePaginationQuery(ChannelFollowings.createQueryBuilder(), ps.sinceId, ps.untilId)
|
||||
.andWhere({ followerId: me.id });
|
||||
|
||||
const followings = await query
|
||||
.take(ps.limit!)
|
||||
.getMany();
|
||||
|
||||
return await Promise.all(followings.map(x => Channels.pack(x.followeeId, me)));
|
||||
});
|
49
packages/backend/src/server/api/endpoints/channels/owned.ts
Normal file
49
packages/backend/src/server/api/endpoints/channels/owned.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import $ from 'cafy';
|
||||
import { ID } from '@/misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { Channels } from '@/models/index';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
|
||||
export const meta = {
|
||||
tags: ['channels', 'account'],
|
||||
|
||||
requireCredential: true as const,
|
||||
|
||||
kind: 'read:channels',
|
||||
|
||||
params: {
|
||||
sinceId: {
|
||||
validator: $.optional.type(ID),
|
||||
},
|
||||
|
||||
untilId: {
|
||||
validator: $.optional.type(ID),
|
||||
},
|
||||
|
||||
limit: {
|
||||
validator: $.optional.num.range(1, 100),
|
||||
default: 5
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'array' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
items: {
|
||||
type: 'object' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
ref: 'Channel',
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, me) => {
|
||||
const query = makePaginationQuery(Channels.createQueryBuilder(), ps.sinceId, ps.untilId)
|
||||
.andWhere({ userId: me.id });
|
||||
|
||||
const channels = await query
|
||||
.take(ps.limit!)
|
||||
.getMany();
|
||||
|
||||
return await Promise.all(channels.map(x => Channels.pack(x, me)));
|
||||
});
|
43
packages/backend/src/server/api/endpoints/channels/show.ts
Normal file
43
packages/backend/src/server/api/endpoints/channels/show.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import $ from 'cafy';
|
||||
import { ID } from '@/misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { ApiError } from '../../error';
|
||||
import { Channels } from '@/models/index';
|
||||
|
||||
export const meta = {
|
||||
tags: ['channels'],
|
||||
|
||||
requireCredential: false as const,
|
||||
|
||||
params: {
|
||||
channelId: {
|
||||
validator: $.type(ID),
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'object' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
ref: 'Channel',
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchChannel: {
|
||||
message: 'No such channel.',
|
||||
code: 'NO_SUCH_CHANNEL',
|
||||
id: '6f6c314b-7486-4897-8966-c04a66a02923'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, me) => {
|
||||
const channel = await Channels.findOne({
|
||||
id: ps.channelId,
|
||||
});
|
||||
|
||||
if (channel == null) {
|
||||
throw new ApiError(meta.errors.noSuchChannel);
|
||||
}
|
||||
|
||||
return await Channels.pack(channel, me);
|
||||
});
|
@@ -0,0 +1,85 @@
|
||||
import $ from 'cafy';
|
||||
import { ID } from '@/misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { ApiError } from '../../error';
|
||||
import { Notes, Channels } from '@/models/index';
|
||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||
import { activeUsersChart } from '@/services/chart/index';
|
||||
|
||||
export const meta = {
|
||||
tags: ['notes', 'channels'],
|
||||
|
||||
requireCredential: false as const,
|
||||
|
||||
params: {
|
||||
channelId: {
|
||||
validator: $.type(ID),
|
||||
},
|
||||
|
||||
limit: {
|
||||
validator: $.optional.num.range(1, 100),
|
||||
default: 10,
|
||||
},
|
||||
|
||||
sinceId: {
|
||||
validator: $.optional.type(ID),
|
||||
},
|
||||
|
||||
untilId: {
|
||||
validator: $.optional.type(ID),
|
||||
},
|
||||
|
||||
sinceDate: {
|
||||
validator: $.optional.num,
|
||||
},
|
||||
|
||||
untilDate: {
|
||||
validator: $.optional.num,
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'array' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
items: {
|
||||
type: 'object' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
ref: 'Note',
|
||||
}
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchChannel: {
|
||||
message: 'No such channel.',
|
||||
code: 'NO_SUCH_CHANNEL',
|
||||
id: '4d0eeeba-a02c-4c3c-9966-ef60d38d2e7f'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
const channel = await Channels.findOne({
|
||||
id: ps.channelId,
|
||||
});
|
||||
|
||||
if (channel == null) {
|
||||
throw new ApiError(meta.errors.noSuchChannel);
|
||||
}
|
||||
|
||||
//#region Construct query
|
||||
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
.andWhere('note.channelId = :channelId', { channelId: channel.id })
|
||||
.innerJoinAndSelect('note.user', 'user')
|
||||
.leftJoinAndSelect('note.reply', 'reply')
|
||||
.leftJoinAndSelect('note.renote', 'renote')
|
||||
.leftJoinAndSelect('reply.user', 'replyUser')
|
||||
.leftJoinAndSelect('renote.user', 'renoteUser')
|
||||
.leftJoinAndSelect('note.channel', 'channel');
|
||||
//#endregion
|
||||
|
||||
const timeline = await query.take(ps.limit!).getMany();
|
||||
|
||||
if (user) activeUsersChart.update(user);
|
||||
|
||||
return await Notes.packMany(timeline, user);
|
||||
});
|
@@ -0,0 +1,45 @@
|
||||
import $ from 'cafy';
|
||||
import { ID } from '@/misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { ApiError } from '../../error';
|
||||
import { Channels, ChannelFollowings } from '@/models/index';
|
||||
import { publishUserEvent } from '@/services/stream';
|
||||
|
||||
export const meta = {
|
||||
tags: ['channels'],
|
||||
|
||||
requireCredential: true as const,
|
||||
|
||||
kind: 'write:channels',
|
||||
|
||||
params: {
|
||||
channelId: {
|
||||
validator: $.type(ID),
|
||||
},
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchChannel: {
|
||||
message: 'No such channel.',
|
||||
code: 'NO_SUCH_CHANNEL',
|
||||
id: '19959ee9-0153-4c51-bbd9-a98c49dc59d6'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, user) => {
|
||||
const channel = await Channels.findOne({
|
||||
id: ps.channelId,
|
||||
});
|
||||
|
||||
if (channel == null) {
|
||||
throw new ApiError(meta.errors.noSuchChannel);
|
||||
}
|
||||
|
||||
await ChannelFollowings.delete({
|
||||
followerId: user.id,
|
||||
followeeId: channel.id,
|
||||
});
|
||||
|
||||
publishUserEvent(user.id, 'unfollowChannel', channel);
|
||||
});
|
94
packages/backend/src/server/api/endpoints/channels/update.ts
Normal file
94
packages/backend/src/server/api/endpoints/channels/update.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import $ from 'cafy';
|
||||
import { ID } from '@/misc/cafy-id';
|
||||
import define from '../../define';
|
||||
import { ApiError } from '../../error';
|
||||
import { Channels, DriveFiles } from '@/models/index';
|
||||
|
||||
export const meta = {
|
||||
tags: ['channels'],
|
||||
|
||||
requireCredential: true as const,
|
||||
|
||||
kind: 'write:channels',
|
||||
|
||||
params: {
|
||||
channelId: {
|
||||
validator: $.type(ID),
|
||||
},
|
||||
|
||||
name: {
|
||||
validator: $.optional.str.range(1, 128)
|
||||
},
|
||||
|
||||
description: {
|
||||
validator: $.nullable.optional.str.range(1, 2048)
|
||||
},
|
||||
|
||||
bannerId: {
|
||||
validator: $.nullable.optional.type(ID),
|
||||
}
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'object' as const,
|
||||
optional: false as const, nullable: false as const,
|
||||
ref: 'Channel',
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchChannel: {
|
||||
message: 'No such channel.',
|
||||
code: 'NO_SUCH_CHANNEL',
|
||||
id: 'f9c5467f-d492-4c3c-9a8d-a70dacc86512'
|
||||
},
|
||||
|
||||
accessDenied: {
|
||||
message: 'You do not have edit privilege of the channel.',
|
||||
code: 'ACCESS_DENIED',
|
||||
id: '1fb7cb09-d46a-4fdf-b8df-057788cce513'
|
||||
},
|
||||
|
||||
noSuchFile: {
|
||||
message: 'No such file.',
|
||||
code: 'NO_SUCH_FILE',
|
||||
id: 'e86c14a4-0da2-4032-8df3-e737a04c7f3b'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, async (ps, me) => {
|
||||
const channel = await Channels.findOne({
|
||||
id: ps.channelId,
|
||||
});
|
||||
|
||||
if (channel == null) {
|
||||
throw new ApiError(meta.errors.noSuchChannel);
|
||||
}
|
||||
|
||||
if (channel.userId !== me.id) {
|
||||
throw new ApiError(meta.errors.accessDenied);
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-unnecessary-initializer
|
||||
let banner = undefined;
|
||||
if (ps.bannerId != null) {
|
||||
banner = await DriveFiles.findOne({
|
||||
id: ps.bannerId,
|
||||
userId: me.id
|
||||
});
|
||||
|
||||
if (banner == null) {
|
||||
throw new ApiError(meta.errors.noSuchFile);
|
||||
}
|
||||
} else if (ps.bannerId === null) {
|
||||
banner = null;
|
||||
}
|
||||
|
||||
await Channels.update(channel.id, {
|
||||
...(ps.name !== undefined ? { name: ps.name } : {}),
|
||||
...(ps.description !== undefined ? { description: ps.description } : {}),
|
||||
...(banner ? { bannerId: banner.id } : {}),
|
||||
});
|
||||
|
||||
return await Channels.pack(channel.id, me);
|
||||
});
|
Reference in New Issue
Block a user