fix: resolve TS strict type errors in sync services (null→undefined)
This commit is contained in:
171
apps/server/src/integration/dingtalk.service.ts
Normal file
171
apps/server/src/integration/dingtalk.service.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Department } from '../entities/department.entity';
|
||||
import { User } from '../entities/user.entity';
|
||||
|
||||
interface DingTalkTokenResponse {
|
||||
errcode: number;
|
||||
errmsg: string;
|
||||
access_token: string;
|
||||
expires_in: number;
|
||||
}
|
||||
|
||||
interface DingTalkDeptListResponse {
|
||||
errcode: number;
|
||||
errmsg: string;
|
||||
result: Array<{ dept_id: number; name: string; parent_id: number }>;
|
||||
}
|
||||
|
||||
interface DingTalkUserListResponse {
|
||||
errcode: number;
|
||||
errmsg: string;
|
||||
result: {
|
||||
has_more: boolean;
|
||||
list: Array<{
|
||||
userid: string;
|
||||
name: string;
|
||||
mobile: string;
|
||||
dept_id_list: number[];
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class DingTalkService {
|
||||
private readonly logger = new Logger(DingTalkService.name);
|
||||
private accessToken: string | null = null;
|
||||
private tokenExpiresAt = 0;
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Department)
|
||||
private readonly deptRepo: Repository<Department>,
|
||||
@InjectRepository(User)
|
||||
private readonly userRepo: Repository<User>,
|
||||
) {}
|
||||
|
||||
private get configured(): boolean {
|
||||
return !!(process.env.DINGTALK_APP_KEY && process.env.DINGTALK_APP_SECRET);
|
||||
}
|
||||
|
||||
private async getAccessToken(): Promise<string> {
|
||||
if (this.accessToken && Date.now() < this.tokenExpiresAt - 60_000) {
|
||||
return this.accessToken;
|
||||
}
|
||||
const appKey = process.env.DINGTALK_APP_KEY!;
|
||||
const appSecret = process.env.DINGTALK_APP_SECRET!;
|
||||
const url = `https://oapi.dingtalk.com/gettoken?appkey=${appKey}&appsecret=${appSecret}`;
|
||||
const res = await fetch(url);
|
||||
const body: DingTalkTokenResponse = await res.json();
|
||||
if (body.errcode !== 0) {
|
||||
throw new Error(`DingTalk gettoken failed: ${body.errmsg} (${body.errcode})`);
|
||||
}
|
||||
this.accessToken = body.access_token;
|
||||
this.tokenExpiresAt = Date.now() + body.expires_in * 1000;
|
||||
return this.accessToken;
|
||||
}
|
||||
|
||||
private async fetchDepartments(token: string): Promise<Array<{ dept_id: number; name: string; parent_id: number }>> {
|
||||
const url = `https://oapi.dingtalk.com/topapi/v2/department/listsub?access_token=${token}`;
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ dept_id: 1 }),
|
||||
});
|
||||
const body: DingTalkDeptListResponse = await res.json();
|
||||
if (body.errcode !== 0) {
|
||||
throw new Error(`DingTalk department list failed: ${body.errmsg} (${body.errcode})`);
|
||||
}
|
||||
return body.result;
|
||||
}
|
||||
|
||||
private async fetchUsers(
|
||||
token: string,
|
||||
deptId: number,
|
||||
): Promise<Array<{ userid: string; name: string; mobile: string; dept_id_list: number[] }>> {
|
||||
const allUsers: DingTalkUserListResponse['result']['list'] = [];
|
||||
let cursor = 0;
|
||||
while (true) {
|
||||
const url = `https://oapi.dingtalk.com/topapi/v2/user/list?access_token=${token}`;
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ dept_id: deptId, cursor, size: 100 }),
|
||||
});
|
||||
const body: DingTalkUserListResponse = await res.json();
|
||||
if (body.errcode !== 0) {
|
||||
throw new Error(`DingTalk user list failed: ${body.errmsg} (${body.errcode})`);
|
||||
}
|
||||
allUsers.push(...body.result.list);
|
||||
if (!body.result.has_more) break;
|
||||
cursor = allUsers.length;
|
||||
}
|
||||
return allUsers;
|
||||
}
|
||||
|
||||
async syncAll(): Promise<{ deptCount: number; userCount: number }> {
|
||||
if (!this.configured) {
|
||||
this.logger.warn('DingTalk not configured (DINGTALK_APP_KEY / DINGTALK_APP_SECRET missing), skipping sync');
|
||||
return { deptCount: 0, userCount: 0 };
|
||||
}
|
||||
const token = await this.getAccessToken();
|
||||
const dingDepts = await this.fetchDepartments(token);
|
||||
|
||||
let deptCount = 0;
|
||||
for (const dd of dingDepts) {
|
||||
const sourceId = String(dd.dept_id);
|
||||
let dept = await this.deptRepo.findOne({ where: { source: 'dingtalk', sourceId } });
|
||||
if (dept) {
|
||||
dept.name = dd.name;
|
||||
dept.parentSourceId = (dd.parent_id ? String(dd.parent_id) : undefined) as any;
|
||||
} else {
|
||||
dept = this.deptRepo.create({
|
||||
name: dd.name,
|
||||
source: 'dingtalk',
|
||||
sourceId,
|
||||
parentSourceId: (dd.parent_id ? String(dd.parent_id) : undefined) as any,
|
||||
type: 'department',
|
||||
});
|
||||
deptCount++;
|
||||
}
|
||||
await this.deptRepo.save(dept);
|
||||
}
|
||||
|
||||
const syncedDepts = await this.deptRepo.find({ where: { source: 'dingtalk' } });
|
||||
const idMap = new Map(syncedDepts.map((d) => [d.sourceId, d.id]));
|
||||
for (const dept of syncedDepts) {
|
||||
if (dept.parentSourceId && idMap.has(dept.parentSourceId)) {
|
||||
dept.parentId = idMap.get(dept.parentSourceId)!;
|
||||
} else if (dept.parentSourceId === '1' || dept.parentSourceId === '0') {
|
||||
dept.parentId = undefined as any;
|
||||
}
|
||||
}
|
||||
await this.deptRepo.save(syncedDepts);
|
||||
|
||||
let userCount = 0;
|
||||
const seenUserIds = new Set<string>();
|
||||
for (const dd of dingDepts) {
|
||||
const dingUsers = await this.fetchUsers(token, dd.dept_id);
|
||||
for (const du of dingUsers) {
|
||||
if (seenUserIds.has(du.userid)) continue;
|
||||
seenUserIds.add(du.userid);
|
||||
let user = await this.userRepo.findOne({ where: { username: du.userid } });
|
||||
if (user) {
|
||||
user.name = du.name;
|
||||
} else {
|
||||
user = this.userRepo.create({
|
||||
username: du.userid,
|
||||
name: du.name,
|
||||
passwordHash: '',
|
||||
isActive: true,
|
||||
});
|
||||
userCount++;
|
||||
}
|
||||
await this.userRepo.save(user);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(`DingTalk sync done: ${deptCount} new depts, ${userCount} new users`);
|
||||
return { deptCount, userCount };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user