import.js
4.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
* import.js — 多源数据导入 REST 路由
*
* POST /api/import/linkedin body: { filePath }
* POST /api/import/x body: { filePath, personNameOrId }
* POST /api/import/meeting body: { title?, date?, summary, rawText?, participants? }
*/
const express = require('express');
const router = express.Router();
const { LinkedInReader } = require('../services/linkedin/linkedin-reader');
const { XReader } = require('../services/x/x-reader');
const { MeetingImporter } = require('../services/meeting/meeting-importer');
const { getGraphStore } = require('../db/GraphStore');
// ── POST /api/import/linkedin ────────────────────────────────────────
router.post('/linkedin', (req, res) => {
try {
const { filePath } = req.body;
if (!filePath) {
return res.status(400).json({ error: 'filePath 为必填字段' });
}
const reader = new LinkedInReader(filePath);
const contacts = reader.parse();
const g = getGraphStore();
let created = 0;
let updated = 0;
for (const c of contacts) {
const fullName = [c.firstName, c.lastName].filter(Boolean).join(' ');
if (!fullName) continue;
const existing = g.findByName(fullName, 'Person');
const id = existing[0]?.id;
g.createNode('Person', {
...(id ? { id } : {}),
name: fullName,
email: c.email || undefined,
company: c.company || undefined,
role: c.position || undefined,
linkedin: c.connectedOn ? `connected:${c.connectedOn}` : undefined,
source: 'linkedin',
});
if (id) updated++; else created++;
}
res.json({
success: true,
total: contacts.length,
created,
updated,
message: `LinkedIn 导入完成:新建 ${created} 人,更新 ${updated} 人`,
});
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// ── POST /api/import/x ───────────────────────────────────────────────
router.post('/x', (req, res) => {
try {
const { filePath, personNameOrId } = req.body;
if (!filePath || !personNameOrId) {
return res.status(400).json({ error: 'filePath 和 personNameOrId 为必填字段' });
}
const g = getGraphStore();
// 解析联系人
let person = g.getNode(personNameOrId);
if (!person) {
const results = g.findByName(personNameOrId, 'Person');
person = results[0] || null;
}
if (!person) {
return res.status(404).json({ error: `未找到联系人:${personNameOrId}` });
}
const reader = new XReader(filePath);
const tweets = reader.parseTweets();
let imported = 0;
for (const tw of tweets) {
const eventNode = g.createNode('Event', {
type: 'post',
summary: tw.text.slice(0, 140),
rawText: tw.text,
date: tw.createdAt
? new Date(tw.createdAt).toISOString().slice(0, 10)
: undefined,
tweetId: tw.id,
hashtags: tw.hashtags,
source: 'x_twitter',
});
g.createEdge(person.id, eventNode.id, 'ASSOCIATED');
imported++;
}
res.json({
success: true,
imported,
personId: person.id,
personName: person.name,
message: `X 动态导入完成:共导入 ${imported} 条推文`,
});
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// ── POST /api/import/meeting ─────────────────────────────────────────
router.post('/meeting', (req, res) => {
try {
const { title, date, summary, rawText, participants } = req.body;
if (!summary) {
return res.status(400).json({ error: 'summary 为必填字段' });
}
const importer = new MeetingImporter();
const result = importer.importMeetingNotes({ title, date, summary, rawText, participants });
res.json({
success: true,
eventId: result.eventId,
linkedParticipants: result.linkedParticipants,
skippedParticipants: result.skippedParticipants,
message: `会议纪要已导入,关联 ${result.linkedParticipants.length} 位参与者`,
});
} catch (err) {
res.status(500).json({ error: err.message });
}
});
module.exports = router;