@timmeck/marketing-brain 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -13
- package/dist/cli/colors.d.ts +11 -24
- package/dist/cli/colors.js +3 -46
- package/dist/cli/colors.js.map +1 -1
- package/dist/cli/commands/dashboard.js +1 -1
- package/dist/cli/commands/peers.d.ts +2 -0
- package/dist/cli/commands/peers.js +38 -0
- package/dist/cli/commands/peers.js.map +1 -0
- package/dist/config.js +3 -3
- package/dist/db/connection.d.ts +1 -2
- package/dist/db/connection.js +1 -18
- package/dist/db/connection.js.map +1 -1
- package/dist/hooks/post-tool-use.d.ts +2 -0
- package/dist/hooks/post-tool-use.js +182 -0
- package/dist/hooks/post-tool-use.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/ipc/__tests__/protocol.test.d.ts +1 -0
- package/dist/ipc/__tests__/protocol.test.js +129 -0
- package/dist/ipc/__tests__/protocol.test.js.map +1 -0
- package/dist/ipc/client.d.ts +1 -13
- package/dist/ipc/client.js +1 -92
- package/dist/ipc/client.js.map +1 -1
- package/dist/ipc/protocol.d.ts +1 -8
- package/dist/ipc/protocol.js +1 -28
- package/dist/ipc/protocol.js.map +1 -1
- package/dist/ipc/router.js +8 -0
- package/dist/ipc/router.js.map +1 -1
- package/dist/ipc/server.d.ts +1 -14
- package/dist/ipc/server.js +1 -129
- package/dist/ipc/server.js.map +1 -1
- package/dist/marketing-core.d.ts +1 -0
- package/dist/marketing-core.js +6 -1
- package/dist/marketing-core.js.map +1 -1
- package/dist/mcp/server.js +5 -60
- package/dist/mcp/server.js.map +1 -1
- package/dist/types/ipc.types.d.ts +1 -11
- package/dist/utils/__tests__/hash.test.d.ts +1 -0
- package/dist/utils/__tests__/hash.test.js +30 -0
- package/dist/utils/__tests__/hash.test.js.map +1 -0
- package/dist/utils/__tests__/paths.test.d.ts +1 -0
- package/dist/utils/__tests__/paths.test.js +63 -0
- package/dist/utils/__tests__/paths.test.js.map +1 -0
- package/dist/utils/events.d.ts +4 -8
- package/dist/utils/events.js +2 -14
- package/dist/utils/events.js.map +1 -1
- package/dist/utils/hash.d.ts +1 -1
- package/dist/utils/hash.js +1 -4
- package/dist/utils/hash.js.map +1 -1
- package/dist/utils/logger.d.ts +3 -2
- package/dist/utils/logger.js +8 -35
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/paths.d.ts +2 -1
- package/dist/utils/paths.js +4 -13
- package/dist/utils/paths.js.map +1 -1
- package/package.json +2 -1
- package/.mcp.json +0 -9
- package/src/api/server.ts +0 -86
- package/src/cli/colors.ts +0 -59
- package/src/cli/commands/campaign.ts +0 -66
- package/src/cli/commands/config.ts +0 -168
- package/src/cli/commands/dashboard.ts +0 -165
- package/src/cli/commands/doctor.ts +0 -110
- package/src/cli/commands/export.ts +0 -40
- package/src/cli/commands/import.ts +0 -84
- package/src/cli/commands/insights.ts +0 -44
- package/src/cli/commands/learn.ts +0 -24
- package/src/cli/commands/network.ts +0 -71
- package/src/cli/commands/post.ts +0 -47
- package/src/cli/commands/query.ts +0 -108
- package/src/cli/commands/rules.ts +0 -27
- package/src/cli/commands/start.ts +0 -100
- package/src/cli/commands/status.ts +0 -73
- package/src/cli/commands/stop.ts +0 -33
- package/src/cli/commands/suggest.ts +0 -64
- package/src/cli/ipc-helper.ts +0 -22
- package/src/cli/update-check.ts +0 -63
- package/src/config.ts +0 -110
- package/src/dashboard/renderer.ts +0 -136
- package/src/dashboard/server.ts +0 -140
- package/src/db/connection.ts +0 -22
- package/src/db/migrations/001_core_schema.ts +0 -63
- package/src/db/migrations/002_learning_schema.ts +0 -46
- package/src/db/migrations/003_synapse_schema.ts +0 -27
- package/src/db/migrations/004_insights_schema.ts +0 -38
- package/src/db/migrations/005_fts_indexes.ts +0 -77
- package/src/db/migrations/index.ts +0 -62
- package/src/db/repositories/audience.repository.ts +0 -53
- package/src/db/repositories/campaign.repository.ts +0 -72
- package/src/db/repositories/engagement.repository.ts +0 -108
- package/src/db/repositories/insight.repository.ts +0 -100
- package/src/db/repositories/post.repository.ts +0 -123
- package/src/db/repositories/rule.repository.ts +0 -87
- package/src/db/repositories/strategy.repository.ts +0 -82
- package/src/db/repositories/synapse.repository.ts +0 -148
- package/src/db/repositories/template.repository.ts +0 -76
- package/src/index.ts +0 -69
- package/src/ipc/client.ts +0 -110
- package/src/ipc/protocol.ts +0 -35
- package/src/ipc/router.ts +0 -126
- package/src/ipc/server.ts +0 -140
- package/src/learning/confidence-scorer.ts +0 -36
- package/src/learning/learning-engine.ts +0 -254
- package/src/marketing-core.ts +0 -285
- package/src/mcp/server.ts +0 -72
- package/src/mcp/tools.ts +0 -216
- package/src/research/research-engine.ts +0 -226
- package/src/services/analytics.service.ts +0 -73
- package/src/services/audience.service.ts +0 -40
- package/src/services/campaign.service.ts +0 -80
- package/src/services/insight.service.ts +0 -54
- package/src/services/post.service.ts +0 -116
- package/src/services/rule.service.ts +0 -90
- package/src/services/strategy.service.ts +0 -53
- package/src/services/synapse.service.ts +0 -32
- package/src/services/template.service.ts +0 -50
- package/src/synapses/activation.ts +0 -80
- package/src/synapses/decay.ts +0 -38
- package/src/synapses/hebbian.ts +0 -68
- package/src/synapses/pathfinder.ts +0 -81
- package/src/synapses/synapse-manager.ts +0 -115
- package/src/types/config.types.ts +0 -79
- package/src/types/ipc.types.ts +0 -8
- package/src/types/post.types.ts +0 -156
- package/src/types/synapse.types.ts +0 -43
- package/src/utils/events.ts +0 -44
- package/src/utils/hash.ts +0 -5
- package/src/utils/logger.ts +0 -48
- package/src/utils/paths.ts +0 -19
- package/tsconfig.json +0 -18
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import type Database from 'better-sqlite3';
|
|
2
|
-
import { getLogger } from '../../utils/logger.js';
|
|
3
|
-
import { up as coreSchema } from './001_core_schema.js';
|
|
4
|
-
import { up as learningSchema } from './002_learning_schema.js';
|
|
5
|
-
import { up as synapseSchema } from './003_synapse_schema.js';
|
|
6
|
-
import { up as insightsSchema } from './004_insights_schema.js';
|
|
7
|
-
import { up as ftsIndexes } from './005_fts_indexes.js';
|
|
8
|
-
|
|
9
|
-
interface Migration {
|
|
10
|
-
version: number;
|
|
11
|
-
name: string;
|
|
12
|
-
up: (db: Database.Database) => void;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const migrations: Migration[] = [
|
|
16
|
-
{ version: 1, name: '001_core_schema', up: coreSchema },
|
|
17
|
-
{ version: 2, name: '002_learning_schema', up: learningSchema },
|
|
18
|
-
{ version: 3, name: '003_synapse_schema', up: synapseSchema },
|
|
19
|
-
{ version: 4, name: '004_insights_schema', up: insightsSchema },
|
|
20
|
-
{ version: 5, name: '005_fts_indexes', up: ftsIndexes },
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
function ensureMigrationsTable(db: Database.Database): void {
|
|
24
|
-
db.exec(`
|
|
25
|
-
CREATE TABLE IF NOT EXISTS migrations (
|
|
26
|
-
version INTEGER PRIMARY KEY,
|
|
27
|
-
name TEXT NOT NULL,
|
|
28
|
-
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
29
|
-
);
|
|
30
|
-
`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function getCurrentVersion(db: Database.Database): number {
|
|
34
|
-
const row = db.prepare('SELECT MAX(version) as version FROM migrations').get() as { version: number | null } | undefined;
|
|
35
|
-
return row?.version ?? 0;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function runMigrations(db: Database.Database): void {
|
|
39
|
-
const logger = getLogger();
|
|
40
|
-
ensureMigrationsTable(db);
|
|
41
|
-
|
|
42
|
-
const currentVersion = getCurrentVersion(db);
|
|
43
|
-
const pending = migrations.filter(m => m.version > currentVersion);
|
|
44
|
-
|
|
45
|
-
if (pending.length === 0) {
|
|
46
|
-
logger.info('Database is up to date');
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
logger.info(`Running ${pending.length} migration(s) from version ${currentVersion}`);
|
|
51
|
-
|
|
52
|
-
const runAll = db.transaction(() => {
|
|
53
|
-
for (const migration of pending) {
|
|
54
|
-
logger.info(`Applying migration ${migration.name}`);
|
|
55
|
-
migration.up(db);
|
|
56
|
-
db.prepare('INSERT INTO migrations (version, name) VALUES (?, ?)').run(migration.version, migration.name);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
runAll();
|
|
61
|
-
logger.info(`Migrations complete. Now at version ${pending[pending.length - 1]!.version}`);
|
|
62
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import type Database from 'better-sqlite3';
|
|
2
|
-
import type { Statement } from 'better-sqlite3';
|
|
3
|
-
import type { Audience } from '../../types/post.types.js';
|
|
4
|
-
|
|
5
|
-
export class AudienceRepository {
|
|
6
|
-
private stmts: Record<string, Statement>;
|
|
7
|
-
|
|
8
|
-
constructor(private db: Database.Database) {
|
|
9
|
-
this.stmts = {
|
|
10
|
-
create: db.prepare(`
|
|
11
|
-
INSERT INTO audiences (name, platform, demographics, interests)
|
|
12
|
-
VALUES (@name, @platform, @demographics, @interests)
|
|
13
|
-
`),
|
|
14
|
-
getById: db.prepare('SELECT * FROM audiences WHERE id = ?'),
|
|
15
|
-
getByName: db.prepare('SELECT * FROM audiences WHERE name = ?'),
|
|
16
|
-
listAll: db.prepare('SELECT * FROM audiences ORDER BY created_at DESC'),
|
|
17
|
-
countAll: db.prepare('SELECT COUNT(*) as count FROM audiences'),
|
|
18
|
-
delete: db.prepare('DELETE FROM audiences WHERE id = ?'),
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
create(data: { name: string; platform?: string; demographics?: string; interests?: string }): number {
|
|
23
|
-
const result = this.stmts.create.run({
|
|
24
|
-
name: data.name,
|
|
25
|
-
platform: data.platform ?? null,
|
|
26
|
-
demographics: data.demographics ?? null,
|
|
27
|
-
interests: data.interests ?? null,
|
|
28
|
-
});
|
|
29
|
-
return result.lastInsertRowid as number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
getById(id: number): Audience | undefined {
|
|
33
|
-
return this.stmts.getById.get(id) as Audience | undefined;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
getByName(name: string): Audience | undefined {
|
|
37
|
-
return this.stmts.getByName.get(name) as Audience | undefined;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
listAll(): Audience[] {
|
|
41
|
-
return this.stmts.listAll.all() as Audience[];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
countAll(): number {
|
|
45
|
-
const row = this.stmts.countAll.get() as { count: number };
|
|
46
|
-
return row.count;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
delete(id: number): boolean {
|
|
50
|
-
const result = this.stmts.delete.run(id);
|
|
51
|
-
return result.changes > 0;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import type Database from 'better-sqlite3';
|
|
2
|
-
import type { Statement } from 'better-sqlite3';
|
|
3
|
-
import type { Campaign, CampaignCreate } from '../../types/post.types.js';
|
|
4
|
-
|
|
5
|
-
export class CampaignRepository {
|
|
6
|
-
private stmts: Record<string, Statement>;
|
|
7
|
-
|
|
8
|
-
constructor(private db: Database.Database) {
|
|
9
|
-
this.stmts = {
|
|
10
|
-
create: db.prepare(`
|
|
11
|
-
INSERT INTO campaigns (name, brand, goal, platform)
|
|
12
|
-
VALUES (@name, @brand, @goal, @platform)
|
|
13
|
-
`),
|
|
14
|
-
getById: db.prepare('SELECT * FROM campaigns WHERE id = ?'),
|
|
15
|
-
getByName: db.prepare('SELECT * FROM campaigns WHERE name = ?'),
|
|
16
|
-
listAll: db.prepare('SELECT * FROM campaigns ORDER BY created_at DESC'),
|
|
17
|
-
listActive: db.prepare(`SELECT * FROM campaigns WHERE status = 'active' ORDER BY created_at DESC`),
|
|
18
|
-
countAll: db.prepare('SELECT COUNT(*) as count FROM campaigns'),
|
|
19
|
-
delete: db.prepare('DELETE FROM campaigns WHERE id = ?'),
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
create(data: CampaignCreate): number {
|
|
24
|
-
const result = this.stmts.create.run({
|
|
25
|
-
name: data.name,
|
|
26
|
-
brand: data.brand ?? null,
|
|
27
|
-
goal: data.goal ?? null,
|
|
28
|
-
platform: data.platform ?? null,
|
|
29
|
-
});
|
|
30
|
-
return result.lastInsertRowid as number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
getById(id: number): Campaign | undefined {
|
|
34
|
-
return this.stmts.getById.get(id) as Campaign | undefined;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
getByName(name: string): Campaign | undefined {
|
|
38
|
-
return this.stmts.getByName.get(name) as Campaign | undefined;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
listAll(): Campaign[] {
|
|
42
|
-
return this.stmts.listAll.all() as Campaign[];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
listActive(): Campaign[] {
|
|
46
|
-
return this.stmts.listActive.all() as Campaign[];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
update(id: number, data: Partial<Campaign>): boolean {
|
|
50
|
-
const fields = Object.keys(data).filter(
|
|
51
|
-
(key) => key !== 'id' && key !== 'created_at' && (data as Record<string, unknown>)[key] !== undefined
|
|
52
|
-
);
|
|
53
|
-
if (fields.length === 0) return false;
|
|
54
|
-
|
|
55
|
-
const setClauses = fields.map((f) => `${f} = @${f}`).join(', ');
|
|
56
|
-
const stmt = this.db.prepare(
|
|
57
|
-
`UPDATE campaigns SET ${setClauses}, updated_at = datetime('now') WHERE id = @id`
|
|
58
|
-
);
|
|
59
|
-
const result = stmt.run({ ...data, id });
|
|
60
|
-
return result.changes > 0;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
countAll(): number {
|
|
64
|
-
const row = this.stmts.countAll.get() as { count: number };
|
|
65
|
-
return row.count;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
delete(id: number): boolean {
|
|
69
|
-
const result = this.stmts.delete.run(id);
|
|
70
|
-
return result.changes > 0;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import type Database from 'better-sqlite3';
|
|
2
|
-
import type { Statement } from 'better-sqlite3';
|
|
3
|
-
import type { Engagement, EngagementCreate } from '../../types/post.types.js';
|
|
4
|
-
|
|
5
|
-
export class EngagementRepository {
|
|
6
|
-
private stmts: Record<string, Statement>;
|
|
7
|
-
|
|
8
|
-
constructor(private db: Database.Database) {
|
|
9
|
-
this.stmts = {
|
|
10
|
-
create: db.prepare(`
|
|
11
|
-
INSERT INTO engagement (post_id, likes, comments, shares, impressions, clicks, saves, reach)
|
|
12
|
-
VALUES (@post_id, @likes, @comments, @shares, @impressions, @clicks, @saves, @reach)
|
|
13
|
-
`),
|
|
14
|
-
getById: db.prepare('SELECT * FROM engagement WHERE id = ?'),
|
|
15
|
-
getByPost: db.prepare('SELECT * FROM engagement WHERE post_id = ? ORDER BY timestamp DESC LIMIT 1'),
|
|
16
|
-
listByPost: db.prepare('SELECT * FROM engagement WHERE post_id = ? ORDER BY timestamp DESC'),
|
|
17
|
-
getLatestPerPost: db.prepare(`
|
|
18
|
-
SELECT e.* FROM engagement e
|
|
19
|
-
INNER JOIN (
|
|
20
|
-
SELECT post_id, MAX(timestamp) as max_ts FROM engagement GROUP BY post_id
|
|
21
|
-
) latest ON e.post_id = latest.post_id AND e.timestamp = latest.max_ts
|
|
22
|
-
ORDER BY (e.likes + e.comments + e.shares + e.impressions) DESC
|
|
23
|
-
LIMIT ?
|
|
24
|
-
`),
|
|
25
|
-
topPosts: db.prepare(`
|
|
26
|
-
SELECT e.*, p.platform, p.content, p.published_at FROM engagement e
|
|
27
|
-
INNER JOIN posts p ON p.id = e.post_id
|
|
28
|
-
INNER JOIN (
|
|
29
|
-
SELECT post_id, MAX(timestamp) as max_ts FROM engagement GROUP BY post_id
|
|
30
|
-
) latest ON e.post_id = latest.post_id AND e.timestamp = latest.max_ts
|
|
31
|
-
WHERE p.status = 'published'
|
|
32
|
-
ORDER BY (e.likes + e.comments * 3 + e.shares * 5 + e.clicks * 2) DESC
|
|
33
|
-
LIMIT ?
|
|
34
|
-
`),
|
|
35
|
-
avgByPlatform: db.prepare(`
|
|
36
|
-
SELECT p.platform,
|
|
37
|
-
AVG(e.likes) as avg_likes,
|
|
38
|
-
AVG(e.comments) as avg_comments,
|
|
39
|
-
AVG(e.shares) as avg_shares,
|
|
40
|
-
AVG(e.impressions) as avg_impressions,
|
|
41
|
-
AVG(e.clicks) as avg_clicks,
|
|
42
|
-
COUNT(DISTINCT e.post_id) as post_count
|
|
43
|
-
FROM engagement e
|
|
44
|
-
INNER JOIN posts p ON p.id = e.post_id
|
|
45
|
-
INNER JOIN (
|
|
46
|
-
SELECT post_id, MAX(timestamp) as max_ts FROM engagement GROUP BY post_id
|
|
47
|
-
) latest ON e.post_id = latest.post_id AND e.timestamp = latest.max_ts
|
|
48
|
-
GROUP BY p.platform
|
|
49
|
-
`),
|
|
50
|
-
delete: db.prepare('DELETE FROM engagement WHERE id = ?'),
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
create(data: EngagementCreate): number {
|
|
55
|
-
const result = this.stmts.create.run({
|
|
56
|
-
post_id: data.post_id,
|
|
57
|
-
likes: data.likes ?? 0,
|
|
58
|
-
comments: data.comments ?? 0,
|
|
59
|
-
shares: data.shares ?? 0,
|
|
60
|
-
impressions: data.impressions ?? 0,
|
|
61
|
-
clicks: data.clicks ?? 0,
|
|
62
|
-
saves: data.saves ?? 0,
|
|
63
|
-
reach: data.reach ?? 0,
|
|
64
|
-
});
|
|
65
|
-
return result.lastInsertRowid as number;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
getById(id: number): Engagement | undefined {
|
|
69
|
-
return this.stmts.getById.get(id) as Engagement | undefined;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
getLatestByPost(postId: number): Engagement | undefined {
|
|
73
|
-
return this.stmts.getByPost.get(postId) as Engagement | undefined;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
listByPost(postId: number): Engagement[] {
|
|
77
|
-
return this.stmts.listByPost.all(postId) as Engagement[];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
topPosts(limit: number = 10): Array<Engagement & { platform: string; content: string; published_at: string }> {
|
|
81
|
-
return this.stmts.topPosts.all(limit) as Array<Engagement & { platform: string; content: string; published_at: string }>;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
avgByPlatform(): Array<{
|
|
85
|
-
platform: string;
|
|
86
|
-
avg_likes: number;
|
|
87
|
-
avg_comments: number;
|
|
88
|
-
avg_shares: number;
|
|
89
|
-
avg_impressions: number;
|
|
90
|
-
avg_clicks: number;
|
|
91
|
-
post_count: number;
|
|
92
|
-
}> {
|
|
93
|
-
return this.stmts.avgByPlatform.all() as Array<{
|
|
94
|
-
platform: string;
|
|
95
|
-
avg_likes: number;
|
|
96
|
-
avg_comments: number;
|
|
97
|
-
avg_shares: number;
|
|
98
|
-
avg_impressions: number;
|
|
99
|
-
avg_clicks: number;
|
|
100
|
-
post_count: number;
|
|
101
|
-
}>;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
delete(id: number): boolean {
|
|
105
|
-
const result = this.stmts.delete.run(id);
|
|
106
|
-
return result.changes > 0;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import type Database from 'better-sqlite3';
|
|
2
|
-
import type { Statement } from 'better-sqlite3';
|
|
3
|
-
import type { Insight, InsightCreate } from '../../types/post.types.js';
|
|
4
|
-
|
|
5
|
-
export class InsightRepository {
|
|
6
|
-
private stmts: Record<string, Statement>;
|
|
7
|
-
|
|
8
|
-
constructor(private db: Database.Database) {
|
|
9
|
-
this.stmts = {
|
|
10
|
-
create: db.prepare(`
|
|
11
|
-
INSERT INTO insights (type, title, description, confidence, priority, campaign_id, expires_at)
|
|
12
|
-
VALUES (@type, @title, @description, @confidence, @priority, @campaign_id, @expires_at)
|
|
13
|
-
`),
|
|
14
|
-
getById: db.prepare('SELECT * FROM insights WHERE id = ?'),
|
|
15
|
-
listActive: db.prepare(`
|
|
16
|
-
SELECT * FROM insights
|
|
17
|
-
WHERE active = 1 AND (expires_at IS NULL OR expires_at > datetime('now'))
|
|
18
|
-
ORDER BY priority DESC, confidence DESC
|
|
19
|
-
LIMIT ?
|
|
20
|
-
`),
|
|
21
|
-
listAll: db.prepare('SELECT * FROM insights ORDER BY created_at DESC LIMIT ?'),
|
|
22
|
-
listByType: db.prepare(`
|
|
23
|
-
SELECT * FROM insights WHERE type = ? AND active = 1
|
|
24
|
-
ORDER BY priority DESC LIMIT ?
|
|
25
|
-
`),
|
|
26
|
-
listByCampaign: db.prepare(`
|
|
27
|
-
SELECT * FROM insights WHERE campaign_id = ? AND active = 1
|
|
28
|
-
ORDER BY priority DESC
|
|
29
|
-
`),
|
|
30
|
-
countActive: db.prepare(`
|
|
31
|
-
SELECT COUNT(*) as count FROM insights
|
|
32
|
-
WHERE active = 1 AND (expires_at IS NULL OR expires_at > datetime('now'))
|
|
33
|
-
`),
|
|
34
|
-
countAll: db.prepare('SELECT COUNT(*) as count FROM insights'),
|
|
35
|
-
deactivate: db.prepare('UPDATE insights SET active = 0 WHERE id = ?'),
|
|
36
|
-
expireOld: db.prepare(`
|
|
37
|
-
UPDATE insights SET active = 0
|
|
38
|
-
WHERE active = 1 AND expires_at IS NOT NULL AND expires_at <= datetime('now')
|
|
39
|
-
`),
|
|
40
|
-
delete: db.prepare('DELETE FROM insights WHERE id = ?'),
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
create(data: InsightCreate): number {
|
|
45
|
-
const result = this.stmts.create.run({
|
|
46
|
-
type: data.type,
|
|
47
|
-
title: data.title,
|
|
48
|
-
description: data.description,
|
|
49
|
-
confidence: data.confidence ?? 0.5,
|
|
50
|
-
priority: data.priority ?? 0,
|
|
51
|
-
campaign_id: data.campaign_id ?? null,
|
|
52
|
-
expires_at: data.expires_at ?? null,
|
|
53
|
-
});
|
|
54
|
-
return result.lastInsertRowid as number;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
getById(id: number): Insight | undefined {
|
|
58
|
-
return this.stmts.getById.get(id) as Insight | undefined;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
listActive(limit: number = 20): Insight[] {
|
|
62
|
-
return this.stmts.listActive.all(limit) as Insight[];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
listAll(limit: number = 50): Insight[] {
|
|
66
|
-
return this.stmts.listAll.all(limit) as Insight[];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
listByType(type: string, limit: number = 20): Insight[] {
|
|
70
|
-
return this.stmts.listByType.all(type, limit) as Insight[];
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
listByCampaign(campaignId: number): Insight[] {
|
|
74
|
-
return this.stmts.listByCampaign.all(campaignId) as Insight[];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
deactivate(id: number): void {
|
|
78
|
-
this.stmts.deactivate.run(id);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
expireOld(): number {
|
|
82
|
-
const result = this.stmts.expireOld.run();
|
|
83
|
-
return result.changes;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
countActive(): number {
|
|
87
|
-
const row = this.stmts.countActive.get() as { count: number };
|
|
88
|
-
return row.count;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
countAll(): number {
|
|
92
|
-
const row = this.stmts.countAll.get() as { count: number };
|
|
93
|
-
return row.count;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
delete(id: number): boolean {
|
|
97
|
-
const result = this.stmts.delete.run(id);
|
|
98
|
-
return result.changes > 0;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import type Database from 'better-sqlite3';
|
|
2
|
-
import type { Statement } from 'better-sqlite3';
|
|
3
|
-
import type { Post, PostCreate } from '../../types/post.types.js';
|
|
4
|
-
import { sha256 } from '../../utils/hash.js';
|
|
5
|
-
|
|
6
|
-
export class PostRepository {
|
|
7
|
-
private stmts: Record<string, Statement>;
|
|
8
|
-
|
|
9
|
-
constructor(private db: Database.Database) {
|
|
10
|
-
this.stmts = {
|
|
11
|
-
create: db.prepare(`
|
|
12
|
-
INSERT INTO posts (campaign_id, platform, content, format, hashtags, url, published_at, fingerprint, status)
|
|
13
|
-
VALUES (@campaign_id, @platform, @content, @format, @hashtags, @url, @published_at, @fingerprint, @status)
|
|
14
|
-
`),
|
|
15
|
-
getById: db.prepare('SELECT * FROM posts WHERE id = ?'),
|
|
16
|
-
getByFingerprint: db.prepare('SELECT * FROM posts WHERE fingerprint = ?'),
|
|
17
|
-
listAll: db.prepare('SELECT * FROM posts ORDER BY created_at DESC LIMIT ?'),
|
|
18
|
-
listByPlatform: db.prepare('SELECT * FROM posts WHERE platform = ? ORDER BY created_at DESC LIMIT ?'),
|
|
19
|
-
listByCampaign: db.prepare('SELECT * FROM posts WHERE campaign_id = ? ORDER BY created_at DESC LIMIT ?'),
|
|
20
|
-
listPublished: db.prepare(`SELECT * FROM posts WHERE status = 'published' ORDER BY published_at DESC LIMIT ?`),
|
|
21
|
-
countAll: db.prepare('SELECT COUNT(*) as count FROM posts'),
|
|
22
|
-
countByPlatform: db.prepare('SELECT platform, COUNT(*) as count FROM posts GROUP BY platform'),
|
|
23
|
-
countByStatus: db.prepare('SELECT status, COUNT(*) as count FROM posts GROUP BY status'),
|
|
24
|
-
delete: db.prepare('DELETE FROM posts WHERE id = ?'),
|
|
25
|
-
search: db.prepare(`
|
|
26
|
-
SELECT p.* FROM posts p
|
|
27
|
-
JOIN posts_fts f ON f.rowid = p.id
|
|
28
|
-
WHERE posts_fts MATCH ?
|
|
29
|
-
ORDER BY rank
|
|
30
|
-
LIMIT ?
|
|
31
|
-
`),
|
|
32
|
-
recentPublished: db.prepare(`
|
|
33
|
-
SELECT * FROM posts WHERE status = 'published' AND published_at > ? ORDER BY published_at DESC
|
|
34
|
-
`),
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
create(data: PostCreate): number {
|
|
39
|
-
const fingerprint = sha256(`${data.platform}:${data.content}`);
|
|
40
|
-
const result = this.stmts.create.run({
|
|
41
|
-
campaign_id: data.campaign_id ?? null,
|
|
42
|
-
platform: data.platform,
|
|
43
|
-
content: data.content,
|
|
44
|
-
format: data.format ?? 'text',
|
|
45
|
-
hashtags: data.hashtags ?? null,
|
|
46
|
-
url: data.url ?? null,
|
|
47
|
-
published_at: data.published_at ?? null,
|
|
48
|
-
fingerprint,
|
|
49
|
-
status: data.status ?? 'draft',
|
|
50
|
-
});
|
|
51
|
-
return result.lastInsertRowid as number;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
getById(id: number): Post | undefined {
|
|
55
|
-
return this.stmts.getById.get(id) as Post | undefined;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
getByFingerprint(fingerprint: string): Post | undefined {
|
|
59
|
-
return this.stmts.getByFingerprint.get(fingerprint) as Post | undefined;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
listAll(limit: number = 50): Post[] {
|
|
63
|
-
return this.stmts.listAll.all(limit) as Post[];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
listByPlatform(platform: string, limit: number = 50): Post[] {
|
|
67
|
-
return this.stmts.listByPlatform.all(platform, limit) as Post[];
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
listByCampaign(campaignId: number, limit: number = 50): Post[] {
|
|
71
|
-
return this.stmts.listByCampaign.all(campaignId, limit) as Post[];
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
listPublished(limit: number = 50): Post[] {
|
|
75
|
-
return this.stmts.listPublished.all(limit) as Post[];
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
recentPublished(sinceDate: string): Post[] {
|
|
79
|
-
return this.stmts.recentPublished.all(sinceDate) as Post[];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
update(id: number, data: Partial<Post>): boolean {
|
|
83
|
-
const fields = Object.keys(data).filter(
|
|
84
|
-
(key) => key !== 'id' && key !== 'created_at' && (data as Record<string, unknown>)[key] !== undefined
|
|
85
|
-
);
|
|
86
|
-
if (fields.length === 0) return false;
|
|
87
|
-
|
|
88
|
-
const setClauses = fields.map((f) => `${f} = @${f}`).join(', ');
|
|
89
|
-
const stmt = this.db.prepare(
|
|
90
|
-
`UPDATE posts SET ${setClauses}, updated_at = datetime('now') WHERE id = @id`
|
|
91
|
-
);
|
|
92
|
-
const result = stmt.run({ ...data, id });
|
|
93
|
-
return result.changes > 0;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
delete(id: number): boolean {
|
|
97
|
-
const result = this.stmts.delete.run(id);
|
|
98
|
-
return result.changes > 0;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
search(query: string, limit: number = 20): Post[] {
|
|
102
|
-
return this.stmts.search.all(query, limit) as Post[];
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
countAll(): number {
|
|
106
|
-
const row = this.stmts.countAll.get() as { count: number };
|
|
107
|
-
return row.count;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
countByPlatform(): Record<string, number> {
|
|
111
|
-
const rows = this.stmts.countByPlatform.all() as Array<{ platform: string; count: number }>;
|
|
112
|
-
const result: Record<string, number> = {};
|
|
113
|
-
for (const row of rows) result[row.platform] = row.count;
|
|
114
|
-
return result;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
countByStatus(): Record<string, number> {
|
|
118
|
-
const rows = this.stmts.countByStatus.all() as Array<{ status: string; count: number }>;
|
|
119
|
-
const result: Record<string, number> = {};
|
|
120
|
-
for (const row of rows) result[row.status] = row.count;
|
|
121
|
-
return result;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import type Database from 'better-sqlite3';
|
|
2
|
-
import type { Statement } from 'better-sqlite3';
|
|
3
|
-
import type { MarketingRule, RuleCreate } from '../../types/post.types.js';
|
|
4
|
-
|
|
5
|
-
export class RuleRepository {
|
|
6
|
-
private stmts: Record<string, Statement>;
|
|
7
|
-
|
|
8
|
-
constructor(private db: Database.Database) {
|
|
9
|
-
this.stmts = {
|
|
10
|
-
create: db.prepare(`
|
|
11
|
-
INSERT INTO marketing_rules (pattern, recommendation, confidence)
|
|
12
|
-
VALUES (@pattern, @recommendation, @confidence)
|
|
13
|
-
`),
|
|
14
|
-
getById: db.prepare('SELECT * FROM marketing_rules WHERE id = ?'),
|
|
15
|
-
listAll: db.prepare('SELECT * FROM marketing_rules ORDER BY confidence DESC'),
|
|
16
|
-
listActive: db.prepare(`SELECT * FROM marketing_rules WHERE active = 1 ORDER BY confidence DESC`),
|
|
17
|
-
countAll: db.prepare('SELECT COUNT(*) as count FROM marketing_rules'),
|
|
18
|
-
countActive: db.prepare(`SELECT COUNT(*) as count FROM marketing_rules WHERE active = 1`),
|
|
19
|
-
delete: db.prepare('DELETE FROM marketing_rules WHERE id = ?'),
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
create(data: RuleCreate): number {
|
|
24
|
-
const result = this.stmts.create.run({
|
|
25
|
-
pattern: data.pattern,
|
|
26
|
-
recommendation: data.recommendation,
|
|
27
|
-
confidence: data.confidence ?? 0.5,
|
|
28
|
-
});
|
|
29
|
-
return result.lastInsertRowid as number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
getById(id: number): MarketingRule | undefined {
|
|
33
|
-
return this.stmts.getById.get(id) as MarketingRule | undefined;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
listAll(): MarketingRule[] {
|
|
37
|
-
return this.stmts.listAll.all() as MarketingRule[];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
listActive(): MarketingRule[] {
|
|
41
|
-
return this.stmts.listActive.all() as MarketingRule[];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
update(id: number, data: Partial<MarketingRule>): boolean {
|
|
45
|
-
const fields = Object.keys(data).filter(
|
|
46
|
-
(key) => key !== 'id' && key !== 'created_at' && (data as Record<string, unknown>)[key] !== undefined
|
|
47
|
-
);
|
|
48
|
-
if (fields.length === 0) return false;
|
|
49
|
-
|
|
50
|
-
const setClauses = fields.map((f) => `${f} = @${f}`).join(', ');
|
|
51
|
-
const stmt = this.db.prepare(
|
|
52
|
-
`UPDATE marketing_rules SET ${setClauses}, updated_at = datetime('now') WHERE id = @id`
|
|
53
|
-
);
|
|
54
|
-
const result = stmt.run({ ...data, id });
|
|
55
|
-
return result.changes > 0;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
incrementTrigger(id: number, success: boolean): void {
|
|
59
|
-
const rule = this.getById(id);
|
|
60
|
-
if (!rule) return;
|
|
61
|
-
const data: Record<string, unknown> = {
|
|
62
|
-
trigger_count: rule.trigger_count + 1,
|
|
63
|
-
id,
|
|
64
|
-
};
|
|
65
|
-
if (success) data['success_count'] = rule.success_count + 1;
|
|
66
|
-
|
|
67
|
-
const stmt = this.db.prepare(
|
|
68
|
-
`UPDATE marketing_rules SET trigger_count = @trigger_count${success ? ', success_count = @success_count' : ''}, updated_at = datetime('now') WHERE id = @id`
|
|
69
|
-
);
|
|
70
|
-
stmt.run(data);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
countAll(): number {
|
|
74
|
-
const row = this.stmts.countAll.get() as { count: number };
|
|
75
|
-
return row.count;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
countActive(): number {
|
|
79
|
-
const row = this.stmts.countActive.get() as { count: number };
|
|
80
|
-
return row.count;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
delete(id: number): boolean {
|
|
84
|
-
const result = this.stmts.delete.run(id);
|
|
85
|
-
return result.changes > 0;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import type Database from 'better-sqlite3';
|
|
2
|
-
import type { Statement } from 'better-sqlite3';
|
|
3
|
-
import type { Strategy, StrategyCreate } from '../../types/post.types.js';
|
|
4
|
-
|
|
5
|
-
export class StrategyRepository {
|
|
6
|
-
private stmts: Record<string, Statement>;
|
|
7
|
-
|
|
8
|
-
constructor(private db: Database.Database) {
|
|
9
|
-
this.stmts = {
|
|
10
|
-
create: db.prepare(`
|
|
11
|
-
INSERT INTO strategies (post_id, description, approach, outcome, confidence)
|
|
12
|
-
VALUES (@post_id, @description, @approach, @outcome, @confidence)
|
|
13
|
-
`),
|
|
14
|
-
getById: db.prepare('SELECT * FROM strategies WHERE id = ?'),
|
|
15
|
-
listAll: db.prepare('SELECT * FROM strategies ORDER BY confidence DESC LIMIT ?'),
|
|
16
|
-
listByPost: db.prepare('SELECT * FROM strategies WHERE post_id = ? ORDER BY confidence DESC'),
|
|
17
|
-
countAll: db.prepare('SELECT COUNT(*) as count FROM strategies'),
|
|
18
|
-
search: db.prepare(`
|
|
19
|
-
SELECT s.* FROM strategies s
|
|
20
|
-
JOIN strategies_fts f ON f.rowid = s.id
|
|
21
|
-
WHERE strategies_fts MATCH ?
|
|
22
|
-
ORDER BY rank
|
|
23
|
-
LIMIT ?
|
|
24
|
-
`),
|
|
25
|
-
topByConfidence: db.prepare('SELECT * FROM strategies WHERE confidence >= ? ORDER BY confidence DESC LIMIT ?'),
|
|
26
|
-
delete: db.prepare('DELETE FROM strategies WHERE id = ?'),
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
create(data: StrategyCreate): number {
|
|
31
|
-
const result = this.stmts.create.run({
|
|
32
|
-
post_id: data.post_id ?? null,
|
|
33
|
-
description: data.description,
|
|
34
|
-
approach: data.approach ?? null,
|
|
35
|
-
outcome: data.outcome ?? null,
|
|
36
|
-
confidence: 0.5,
|
|
37
|
-
});
|
|
38
|
-
return result.lastInsertRowid as number;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
getById(id: number): Strategy | undefined {
|
|
42
|
-
return this.stmts.getById.get(id) as Strategy | undefined;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
listAll(limit: number = 50): Strategy[] {
|
|
46
|
-
return this.stmts.listAll.all(limit) as Strategy[];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
listByPost(postId: number): Strategy[] {
|
|
50
|
-
return this.stmts.listByPost.all(postId) as Strategy[];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
search(query: string, limit: number = 20): Strategy[] {
|
|
54
|
-
return this.stmts.search.all(query, limit) as Strategy[];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
topByConfidence(minConfidence: number = 0.7, limit: number = 20): Strategy[] {
|
|
58
|
-
return this.stmts.topByConfidence.all(minConfidence, limit) as Strategy[];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
update(id: number, data: Partial<Strategy>): boolean {
|
|
62
|
-
const fields = Object.keys(data).filter(
|
|
63
|
-
(key) => key !== 'id' && key !== 'created_at' && (data as Record<string, unknown>)[key] !== undefined
|
|
64
|
-
);
|
|
65
|
-
if (fields.length === 0) return false;
|
|
66
|
-
|
|
67
|
-
const setClauses = fields.map((f) => `${f} = @${f}`).join(', ');
|
|
68
|
-
const stmt = this.db.prepare(`UPDATE strategies SET ${setClauses} WHERE id = @id`);
|
|
69
|
-
const result = stmt.run({ ...data, id });
|
|
70
|
-
return result.changes > 0;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
countAll(): number {
|
|
74
|
-
const row = this.stmts.countAll.get() as { count: number };
|
|
75
|
-
return row.count;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
delete(id: number): boolean {
|
|
79
|
-
const result = this.stmts.delete.run(id);
|
|
80
|
-
return result.changes > 0;
|
|
81
|
-
}
|
|
82
|
-
}
|