Add user-scoped data filtering for subscription and dependence operations

Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-11-09 16:37:37 +00:00
parent 4cf2858ab0
commit 777fd3fb23
4 changed files with 106 additions and 42 deletions

View File

@ -22,7 +22,7 @@ export default (app: Router) => {
const logger: Logger = Container.get('logger');
try {
const dependenceService = Container.get(DependenceService);
const data = await dependenceService.dependencies(req.query as any);
const data = await dependenceService.dependencies(req.query as any, [], {}, req.user?.userId);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
@ -45,7 +45,7 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => {
try {
const dependenceService = Container.get(DependenceService);
const data = await dependenceService.create(req.body);
const data = await dependenceService.create(req.body, req.user?.userId);
return res.send({ code: 200, data });
} catch (e) {
return next(e);
@ -82,10 +82,10 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => {
try {
const dependenceService = Container.get(DependenceService);
const data = await dependenceService.remove(req.body);
const data = await dependenceService.remove(req.body, false, req.user?.userId);
return res.send({ code: 200, data });
} catch (e) {
return next(e);
} catch (e: any) {
return res.send({ code: 400, message: e.message });
}
},
);
@ -98,10 +98,10 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => {
try {
const dependenceService = Container.get(DependenceService);
const data = await dependenceService.remove(req.body, true);
const data = await dependenceService.remove(req.body, true, req.user?.userId);
return res.send({ code: 200, data });
} catch (e) {
return next(e);
} catch (e: any) {
return res.send({ code: 400, message: e.message });
}
},
);
@ -132,10 +132,10 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => {
try {
const dependenceService = Container.get(DependenceService);
const data = await dependenceService.reInstall(req.body);
const data = await dependenceService.reInstall(req.body, req.user?.userId);
return res.send({ code: 200, data });
} catch (e) {
return next(e);
} catch (e: any) {
return res.send({ code: 400, message: e.message });
}
},
);
@ -148,10 +148,10 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => {
try {
const dependenceService = Container.get(DependenceService);
await dependenceService.cancel(req.body);
await dependenceService.cancel(req.body, req.user?.userId);
return res.send({ code: 200 });
} catch (e) {
return next(e);
} catch (e: any) {
return res.send({ code: 400, message: e.message });
}
},
);

View File

@ -16,6 +16,7 @@ export default (app: Router) => {
const data = await subscriptionService.list(
req.query.searchValue as string,
req.query.ids as string,
req.user?.userId,
);
return res.send({ code: 200, data });
} catch (e) {
@ -63,7 +64,10 @@ export default (app: Router) => {
cron_parser.parseExpression(req.body.schedule).hasNext()
) {
const subscriptionService = Container.get(SubscriptionService);
const data = await subscriptionService.create(req.body);
const data = await subscriptionService.create({
...req.body,
userId: req.user?.userId,
});
return res.send({ code: 200, data });
} else {
return res.send({ code: 400, message: 'param schedule error' });
@ -83,10 +87,10 @@ export default (app: Router) => {
const logger: Logger = Container.get('logger');
try {
const subscriptionService = Container.get(SubscriptionService);
const data = await subscriptionService.run(req.body);
const data = await subscriptionService.run(req.body, req.user?.userId);
return res.send({ code: 200, data });
} catch (e) {
return next(e);
} catch (e: any) {
return res.send({ code: 400, message: e.message });
}
},
);
@ -100,10 +104,10 @@ export default (app: Router) => {
const logger: Logger = Container.get('logger');
try {
const subscriptionService = Container.get(SubscriptionService);
const data = await subscriptionService.stop(req.body);
const data = await subscriptionService.stop(req.body, req.user?.userId);
return res.send({ code: 200, data });
} catch (e) {
return next(e);
} catch (e: any) {
return res.send({ code: 400, message: e.message });
}
},
);
@ -117,10 +121,10 @@ export default (app: Router) => {
const logger: Logger = Container.get('logger');
try {
const subscriptionService = Container.get(SubscriptionService);
const data = await subscriptionService.disabled(req.body);
const data = await subscriptionService.disabled(req.body, req.user?.userId);
return res.send({ code: 200, data });
} catch (e) {
return next(e);
} catch (e: any) {
return res.send({ code: 400, message: e.message });
}
},
);
@ -134,10 +138,10 @@ export default (app: Router) => {
const logger: Logger = Container.get('logger');
try {
const subscriptionService = Container.get(SubscriptionService);
const data = await subscriptionService.enabled(req.body);
const data = await subscriptionService.enabled(req.body, req.user?.userId);
return res.send({ code: 200, data });
} catch (e) {
return next(e);
} catch (e: any) {
return res.send({ code: 400, message: e.message });
}
},
);
@ -220,10 +224,10 @@ export default (app: Router) => {
const logger: Logger = Container.get('logger');
try {
const subscriptionService = Container.get(SubscriptionService);
const data = await subscriptionService.remove(req.body, req.query);
const data = await subscriptionService.remove(req.body, req.query, req.user?.userId);
return res.send({ code: 200, data });
} catch (e) {
return next(e);
} catch (e: any) {
return res.send({ code: 400, message: e.message });
}
},
);

View File

@ -30,9 +30,33 @@ export default class DependenceService {
private sockService: SockService,
) { }
public async create(payloads: Dependence[]): Promise<Dependence[]> {
private addUserIdFilter(query: any, userId?: number) {
if (userId !== undefined) {
query.userId = userId;
}
return query;
}
private async checkOwnership(ids: number[], userId?: number): Promise<void> {
if (userId === undefined) {
// Admin can access all dependencies
return;
}
const dependencies = await DependenceModel.findAll({
where: { id: ids },
attributes: ['id', 'userId'],
});
const unauthorized = dependencies.filter(
(dep) => dep.userId !== undefined && dep.userId !== userId
);
if (unauthorized.length > 0) {
throw new Error('无权限操作该依赖');
}
}
public async create(payloads: Dependence[], userId?: number): Promise<Dependence[]> {
const tabs = payloads.map((x) => {
const tab = new Dependence({ ...x, status: DependenceStatus.queued });
const tab = new Dependence({ ...x, status: DependenceStatus.queued, userId });
return tab;
});
const docs = await this.insert(tabs);
@ -65,7 +89,8 @@ export default class DependenceService {
return await this.getDb({ id: payload.id });
}
public async remove(ids: number[], force = false): Promise<Dependence[]> {
public async remove(ids: number[], force = false, userId?: number): Promise<Dependence[]> {
await this.checkOwnership(ids, userId);
const docs = await DependenceModel.findAll({ where: { id: ids } });
for (const doc of docs) {
taskLimit.removeQueuedDependency(doc);
@ -105,8 +130,10 @@ export default class DependenceService {
},
sort: any = [],
query: any = {},
userId?: number,
): Promise<Dependence[]> {
let condition = query;
this.addUserIdFilter(condition, userId);
if (DependenceTypes[type]) {
condition.type = DependenceTypes[type];
}
@ -141,7 +168,8 @@ export default class DependenceService {
return taskLimit.waitDependencyQueueDone();
}
public async reInstall(ids: number[]): Promise<Dependence[]> {
public async reInstall(ids: number[], userId?: number): Promise<Dependence[]> {
await this.checkOwnership(ids, userId);
await DependenceModel.update(
{ status: DependenceStatus.queued, log: [] },
{ where: { id: ids } },
@ -155,7 +183,8 @@ export default class DependenceService {
return docs;
}
public async cancel(ids: number[]) {
public async cancel(ids: number[], userId?: number) {
await this.checkOwnership(ids, userId);
const docs = await DependenceModel.findAll({ where: { id: ids } });
for (const doc of docs) {
taskLimit.removeQueuedDependency(doc);

View File

@ -42,11 +42,37 @@ export default class SubscriptionService {
private crontabService: CrontabService,
) {}
private addUserIdFilter(query: any, userId?: number) {
if (userId !== undefined) {
query.userId = userId;
}
return query;
}
private async checkOwnership(ids: number[], userId?: number): Promise<void> {
if (userId === undefined) {
// Admin can access all subscriptions
return;
}
const subscriptions = await SubscriptionModel.findAll({
where: { id: ids },
attributes: ['id', 'userId'],
});
const unauthorized = subscriptions.filter(
(sub) => sub.userId !== undefined && sub.userId !== userId
);
if (unauthorized.length > 0) {
throw new Error('无权限操作该订阅');
}
}
public async list(
searchText?: string,
ids?: string,
userId?: number,
): Promise<SubscriptionInstance[]> {
let query = {};
let query: any = {};
this.addUserIdFilter(query, userId);
const subIds = JSON.parse(ids || '[]');
if (searchText) {
const reg = {
@ -262,7 +288,8 @@ export default class SubscriptionService {
);
}
public async remove(ids: number[], query: { force?: boolean }) {
public async remove(ids: number[], query: { force?: boolean }, userId?: number) {
await this.checkOwnership(ids, userId);
const docs = await SubscriptionModel.findAll({ where: { id: ids } });
for (const doc of docs) {
await this.handleTask(doc.get({ plain: true }), false);
@ -273,7 +300,7 @@ export default class SubscriptionService {
if (query?.force === true) {
const crons = await CrontabModel.findAll({ where: { sub_id: ids } });
if (crons?.length) {
await this.crontabService.remove(crons.map((x) => x.id!));
await this.crontabService.remove(crons.map((x) => x.id!), userId);
}
for (const doc of docs) {
const filePath = join(config.scriptPath, doc.alias);
@ -294,7 +321,8 @@ export default class SubscriptionService {
return doc.get({ plain: true });
}
public async run(ids: number[]) {
public async run(ids: number[], userId?: number) {
await this.checkOwnership(ids, userId);
await SubscriptionModel.update(
{ status: SubscriptionStatus.queued },
{ where: { id: ids } },
@ -304,7 +332,8 @@ export default class SubscriptionService {
});
}
public async stop(ids: number[]) {
public async stop(ids: number[], userId?: number) {
await this.checkOwnership(ids, userId);
const docs = await SubscriptionModel.findAll({ where: { id: ids } });
for (const doc of docs) {
if (doc.pid) {
@ -339,7 +368,8 @@ export default class SubscriptionService {
});
}
public async disabled(ids: number[]) {
public async disabled(ids: number[], userId?: number) {
await this.checkOwnership(ids, userId);
await SubscriptionModel.update({ is_disabled: 1 }, { where: { id: ids } });
const docs = await SubscriptionModel.findAll({ where: { id: ids } });
await this.setSshConfig();
@ -348,7 +378,8 @@ export default class SubscriptionService {
}
}
public async enabled(ids: number[]) {
public async enabled(ids: number[], userId?: number) {
await this.checkOwnership(ids, userId);
await SubscriptionModel.update({ is_disabled: 0 }, { where: { id: ids } });
const docs = await SubscriptionModel.findAll({ where: { id: ids } });
await this.setSshConfig();