@venturekit-pro/social 0.0.0-dev.20260602192622
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/LICENSE +191 -0
- package/README.md +134 -0
- package/dist/adapter.d.ts +46 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +15 -0
- package/dist/adapter.js.map +1 -0
- package/dist/adapters/facebook.d.ts +25 -0
- package/dist/adapters/facebook.d.ts.map +1 -0
- package/dist/adapters/facebook.js +95 -0
- package/dist/adapters/facebook.js.map +1 -0
- package/dist/adapters/index.d.ts +23 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +19 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/instagram.d.ts +25 -0
- package/dist/adapters/instagram.d.ts.map +1 -0
- package/dist/adapters/instagram.js +113 -0
- package/dist/adapters/instagram.js.map +1 -0
- package/dist/adapters/linkedin.d.ts +28 -0
- package/dist/adapters/linkedin.d.ts.map +1 -0
- package/dist/adapters/linkedin.js +121 -0
- package/dist/adapters/linkedin.js.map +1 -0
- package/dist/adapters/x.d.ts +21 -0
- package/dist/adapters/x.d.ts.map +1 -0
- package/dist/adapters/x.js +80 -0
- package/dist/adapters/x.js.map +1 -0
- package/dist/http.d.ts +40 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +127 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations/vk_social_0001_init.sql +121 -0
- package/dist/path.d.ts +9 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +17 -0
- package/dist/path.js.map +1 -0
- package/dist/registry.d.ts +32 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +42 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +229 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +57 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +17 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +157 -0
- package/dist/validation.js.map +1 -0
- package/package.json +78 -0
- package/src/migrations/vk_social_0001_init.sql +121 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic post-validation. Adapters compose this with their per-
|
|
3
|
+
* platform `SocialPlatformConstraints` to surface a uniform error
|
|
4
|
+
* shape without re-implementing every check four times.
|
|
5
|
+
*
|
|
6
|
+
* Returns BOTH errors AND warnings. Callers MUST block publish on
|
|
7
|
+
* `severity === 'error'`; warnings are informational ("you only
|
|
8
|
+
* have 1 hashtag, LinkedIn recommends 2-5").
|
|
9
|
+
*/
|
|
10
|
+
const HASHTAG_PATTERN = /#[\w\u00C0-\u024F\u4E00-\u9FFF]+/g;
|
|
11
|
+
const URL_PATTERN = /^https?:\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-]*)$/;
|
|
12
|
+
export function validatePost(post, constraints) {
|
|
13
|
+
const issues = [];
|
|
14
|
+
// ─── Body length ──────────────────────────────────────────────────
|
|
15
|
+
const body = post.body ?? '';
|
|
16
|
+
const bodyLen = [...body].length; // grapheme-ish count via spread; better than .length for non-Latin
|
|
17
|
+
if (constraints.minBodyChars > 0 && bodyLen === 0) {
|
|
18
|
+
issues.push({
|
|
19
|
+
code: 'body_empty',
|
|
20
|
+
severity: 'error',
|
|
21
|
+
message: 'Post body is empty.',
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
else if (bodyLen < constraints.minBodyChars) {
|
|
25
|
+
issues.push({
|
|
26
|
+
code: 'body_too_short',
|
|
27
|
+
severity: 'error',
|
|
28
|
+
message: `Body too short (${bodyLen} chars; minimum ${constraints.minBodyChars}).`,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (bodyLen > constraints.maxBodyChars) {
|
|
32
|
+
issues.push({
|
|
33
|
+
code: 'body_too_long',
|
|
34
|
+
severity: 'error',
|
|
35
|
+
message: `Body too long (${bodyLen} chars; max ${constraints.maxBodyChars}).`,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
// ─── Hashtags ─────────────────────────────────────────────────────
|
|
39
|
+
const hashtags = body.match(HASHTAG_PATTERN) ?? [];
|
|
40
|
+
if (hashtags.length < constraints.minHashtags) {
|
|
41
|
+
issues.push({
|
|
42
|
+
code: 'hashtag_count_low',
|
|
43
|
+
severity: 'warning',
|
|
44
|
+
message: `Hashtag count below recommendation (${hashtags.length}; min ${constraints.minHashtags}).`,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (hashtags.length > constraints.maxHashtags) {
|
|
48
|
+
issues.push({
|
|
49
|
+
code: 'hashtag_count_high',
|
|
50
|
+
severity: 'warning',
|
|
51
|
+
message: `Hashtag count above recommendation (${hashtags.length}; max ${constraints.maxHashtags}).`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
// ─── Media presence + count ──────────────────────────────────────
|
|
55
|
+
const media = post.media ?? [];
|
|
56
|
+
if (constraints.maxMediaCount === 0 && media.length > 0) {
|
|
57
|
+
issues.push({
|
|
58
|
+
code: 'media_count_high',
|
|
59
|
+
severity: 'error',
|
|
60
|
+
message: 'Platform does not accept media attachments.',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (constraints.maxMediaCount > 0 &&
|
|
64
|
+
media.length > constraints.maxMediaCount) {
|
|
65
|
+
issues.push({
|
|
66
|
+
code: 'media_count_high',
|
|
67
|
+
severity: 'error',
|
|
68
|
+
message: `Too many media items (${media.length}; max ${constraints.maxMediaCount}).`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
// Some platforms (e.g. Instagram) require media on every post.
|
|
72
|
+
if (constraints.allowedMimeTypes.length > 0 &&
|
|
73
|
+
constraints.maxMediaCount > 0 &&
|
|
74
|
+
media.length === 0 &&
|
|
75
|
+
requiresMedia(constraints)) {
|
|
76
|
+
issues.push({
|
|
77
|
+
code: 'media_required',
|
|
78
|
+
severity: 'error',
|
|
79
|
+
message: 'Platform requires at least one media attachment.',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// ─── Per-media validation ────────────────────────────────────────
|
|
83
|
+
for (const m of media) {
|
|
84
|
+
if (constraints.allowedMimeTypes.length > 0 &&
|
|
85
|
+
!constraints.allowedMimeTypes.includes(m.mimeType)) {
|
|
86
|
+
issues.push({
|
|
87
|
+
code: 'media_mime_unsupported',
|
|
88
|
+
severity: 'error',
|
|
89
|
+
message: `Unsupported MIME type: ${m.mimeType}. Allowed: ${constraints.allowedMimeTypes.join(', ')}.`,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (constraints.maxMediaSizeBytes > 0 &&
|
|
93
|
+
m.sizeBytes !== undefined &&
|
|
94
|
+
m.sizeBytes > constraints.maxMediaSizeBytes) {
|
|
95
|
+
issues.push({
|
|
96
|
+
code: 'media_too_large',
|
|
97
|
+
severity: 'error',
|
|
98
|
+
message: `Media too large (${m.sizeBytes} bytes; max ${constraints.maxMediaSizeBytes}).`,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (constraints.allowedAspectRatios.length > 0 &&
|
|
102
|
+
m.width !== undefined &&
|
|
103
|
+
m.height !== undefined) {
|
|
104
|
+
const ratio = simplifyRatio(m.width, m.height);
|
|
105
|
+
if (!constraints.allowedAspectRatios.includes(ratio)) {
|
|
106
|
+
issues.push({
|
|
107
|
+
code: 'aspect_ratio_unsupported',
|
|
108
|
+
severity: 'warning',
|
|
109
|
+
message: `Aspect ratio ${ratio} not in allow-list (${constraints.allowedAspectRatios.join(', ')}).`,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// ─── Link ────────────────────────────────────────────────────────
|
|
115
|
+
if (post.link && !URL_PATTERN.test(post.link)) {
|
|
116
|
+
issues.push({
|
|
117
|
+
code: 'link_invalid',
|
|
118
|
+
severity: 'error',
|
|
119
|
+
message: `Link does not look like a valid URL: ${post.link}`,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const ok = !issues.some((i) => i.severity === 'error');
|
|
123
|
+
return { ok, issues };
|
|
124
|
+
}
|
|
125
|
+
// ─── Internal helpers ──────────────────────────────────────────────────
|
|
126
|
+
/**
|
|
127
|
+
* Whether the platform refuses text-only posts. Reads the explicit
|
|
128
|
+
* `requiresMedia` flag from the constraints (defaults to `false`).
|
|
129
|
+
*
|
|
130
|
+
* The flag is decoupled from `minBodyChars` because Instagram requires
|
|
131
|
+
* BOTH a non-empty caption AND a media item — a heuristic of "media
|
|
132
|
+
* required when minBodyChars === 0" would miss that case.
|
|
133
|
+
*/
|
|
134
|
+
function requiresMedia(c) {
|
|
135
|
+
return c.requiresMedia === true;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Reduce `(width, height)` to a normalized `W:H` ratio string.
|
|
139
|
+
* Matches the format used in `allowedAspectRatios`.
|
|
140
|
+
*/
|
|
141
|
+
export function simplifyRatio(width, height) {
|
|
142
|
+
if (width <= 0 || height <= 0)
|
|
143
|
+
return `${width}:${height}`;
|
|
144
|
+
const g = gcd(width, height);
|
|
145
|
+
return `${width / g}:${height / g}`;
|
|
146
|
+
}
|
|
147
|
+
function gcd(a, b) {
|
|
148
|
+
let x = Math.abs(Math.trunc(a));
|
|
149
|
+
let y = Math.abs(Math.trunc(b));
|
|
150
|
+
while (y) {
|
|
151
|
+
const t = y;
|
|
152
|
+
y = x % y;
|
|
153
|
+
x = t;
|
|
154
|
+
}
|
|
155
|
+
return x || 1;
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@venturekit-pro/social",
|
|
3
|
+
"version": "0.0.0-dev.20260602192622",
|
|
4
|
+
"description": "Social-platform publishing adapters + catalog for VentureKit applications",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"src/migrations/*.sql"
|
|
11
|
+
],
|
|
12
|
+
"vk": {
|
|
13
|
+
"migrations": "src/migrations"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/venturekit-dev/venturekit.private.git",
|
|
18
|
+
"directory": "packages/pro/social"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"registry": "https://registry.npmjs.org",
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"license": "SEE LICENSE IN ../LICENSE",
|
|
25
|
+
"licenseHeader": "VentureKit Pro Commercial License — production use requires a valid VentureKit Pro license key. See https://venturekit.dev/pricing.",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts"
|
|
30
|
+
},
|
|
31
|
+
"./adapters/linkedin": {
|
|
32
|
+
"import": "./dist/adapters/linkedin.js",
|
|
33
|
+
"types": "./dist/adapters/linkedin.d.ts"
|
|
34
|
+
},
|
|
35
|
+
"./adapters/x": {
|
|
36
|
+
"import": "./dist/adapters/x.js",
|
|
37
|
+
"types": "./dist/adapters/x.d.ts"
|
|
38
|
+
},
|
|
39
|
+
"./adapters/facebook": {
|
|
40
|
+
"import": "./dist/adapters/facebook.js",
|
|
41
|
+
"types": "./dist/adapters/facebook.d.ts"
|
|
42
|
+
},
|
|
43
|
+
"./adapters/instagram": {
|
|
44
|
+
"import": "./dist/adapters/instagram.js",
|
|
45
|
+
"types": "./dist/adapters/instagram.d.ts"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"keywords": [
|
|
49
|
+
"venturekit",
|
|
50
|
+
"saas",
|
|
51
|
+
"social",
|
|
52
|
+
"linkedin",
|
|
53
|
+
"twitter",
|
|
54
|
+
"meta",
|
|
55
|
+
"publishing"
|
|
56
|
+
],
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@venturekit/core": "0.0.0-dev.20260602192622"
|
|
59
|
+
},
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"@venturekit/data": "0.0.0-dev.20260602192622"
|
|
62
|
+
},
|
|
63
|
+
"peerDependenciesMeta": {
|
|
64
|
+
"@venturekit/data": {
|
|
65
|
+
"optional": true
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@types/node": "^25.6.0",
|
|
70
|
+
"@venturekit/data": "0.0.0-dev.20260602192622",
|
|
71
|
+
"typescript": "^5.3.0"
|
|
72
|
+
},
|
|
73
|
+
"scripts": {
|
|
74
|
+
"build": "tsc && node -e \"require('fs').cpSync('src/migrations','dist/migrations',{recursive:true})\"",
|
|
75
|
+
"dev": "tsc --watch",
|
|
76
|
+
"clean": "rm -rf dist"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
-- @venturekit-pro/social — initial schema.
|
|
2
|
+
--
|
|
3
|
+
-- Two tables:
|
|
4
|
+
--
|
|
5
|
+
-- social_platforms — platform-admin-owned catalog of every
|
|
6
|
+
-- social network the package supports.
|
|
7
|
+
-- One row per (provider, adapter version).
|
|
8
|
+
-- The four v1 platforms are seeded by the
|
|
9
|
+
-- bottom of this migration.
|
|
10
|
+
--
|
|
11
|
+
-- tenant_social_platforms — per-tenant enable/disable + author-ref
|
|
12
|
+
-- pointer. NO cadence / fan-out / locale
|
|
13
|
+
-- fields here — those are calling-app
|
|
14
|
+
-- concerns. Apps that want per-platform
|
|
15
|
+
-- cadence settings model that in their
|
|
16
|
+
-- own schema.
|
|
17
|
+
--
|
|
18
|
+
-- Append-only fields are not used here; the rows are tenant-mutable
|
|
19
|
+
-- (toggle enabled, swap author_ref). Audit-worthy changes flow into
|
|
20
|
+
-- `@venturekit-pro/audit`'s `audit_events` separately.
|
|
21
|
+
|
|
22
|
+
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
23
|
+
|
|
24
|
+
-- ─── social_platforms (catalog) ─────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
DO $$
|
|
27
|
+
BEGIN
|
|
28
|
+
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'vk_social_platform_status') THEN
|
|
29
|
+
CREATE TYPE vk_social_platform_status AS ENUM (
|
|
30
|
+
'available',
|
|
31
|
+
'deprecated',
|
|
32
|
+
'disabled'
|
|
33
|
+
);
|
|
34
|
+
END IF;
|
|
35
|
+
END
|
|
36
|
+
$$;
|
|
37
|
+
|
|
38
|
+
CREATE TABLE IF NOT EXISTS social_platforms (
|
|
39
|
+
key varchar(64) PRIMARY KEY,
|
|
40
|
+
display_name varchar(255) NOT NULL,
|
|
41
|
+
-- Lifecycle: `available` rows render in pickers; `deprecated` ones
|
|
42
|
+
-- are still callable but the UI nudges a swap; `disabled` rows
|
|
43
|
+
-- refuse to publish at the adapter resolution boundary.
|
|
44
|
+
status vk_social_platform_status NOT NULL DEFAULT 'available',
|
|
45
|
+
-- Human-readable description for picker rows.
|
|
46
|
+
description text,
|
|
47
|
+
-- Free-form metadata (icon name, brand color, etc). Apps consume
|
|
48
|
+
-- this for UI rendering without the package needing to enumerate
|
|
49
|
+
-- every visual property.
|
|
50
|
+
meta jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
51
|
+
-- When the row was first introduced + lifecycle timestamps. Used
|
|
52
|
+
-- by the platform-admin UI to render badges.
|
|
53
|
+
introduced_at timestamptz NOT NULL DEFAULT now(),
|
|
54
|
+
deprecated_at timestamptz,
|
|
55
|
+
disabled_at timestamptz,
|
|
56
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
57
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
-- ─── tenant_social_platforms (per-tenant) ───────────────────────────
|
|
61
|
+
--
|
|
62
|
+
-- Apps that want per-platform-per-tenant settings (cadence, locales,
|
|
63
|
+
-- fan-out weeks) extend THIS table in their own migration. We keep
|
|
64
|
+
-- the package-owned columns to the strict minimum: enabled flag,
|
|
65
|
+
-- author ref (the platform-side id we publish into), and optional
|
|
66
|
+
-- vendor metadata.
|
|
67
|
+
|
|
68
|
+
CREATE TABLE IF NOT EXISTS tenant_social_platforms (
|
|
69
|
+
tenant_id uuid NOT NULL,
|
|
70
|
+
platform_key varchar(64) NOT NULL REFERENCES social_platforms(key) ON DELETE RESTRICT,
|
|
71
|
+
enabled boolean NOT NULL DEFAULT TRUE,
|
|
72
|
+
-- Platform-side identifier this tenant publishes to. URN / page id /
|
|
73
|
+
-- ig user id. Required when enabled = TRUE; the application enforces
|
|
74
|
+
-- that invariant at the service boundary (we don't put a CHECK here
|
|
75
|
+
-- because the empty string is sometimes used as a placeholder
|
|
76
|
+
-- during onboarding wizards).
|
|
77
|
+
author_ref varchar(512) NOT NULL DEFAULT '',
|
|
78
|
+
-- Free-form metadata stamped by the tenant ops flow (locale,
|
|
79
|
+
-- scopes, etc). Adapters ignore unless documented.
|
|
80
|
+
meta jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
81
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
82
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
83
|
+
PRIMARY KEY (tenant_id, platform_key)
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
CREATE INDEX IF NOT EXISTS tenant_social_platforms_enabled_idx
|
|
87
|
+
ON tenant_social_platforms (tenant_id)
|
|
88
|
+
WHERE enabled;
|
|
89
|
+
|
|
90
|
+
-- ─── Catalog seed ───────────────────────────────────────────────────
|
|
91
|
+
--
|
|
92
|
+
-- Idempotent (ON CONFLICT DO NOTHING) so re-running the migration on
|
|
93
|
+
-- an already-seeded DB is a no-op. Tenants who want to add custom
|
|
94
|
+
-- adapters (e.g. Mastodon, Bluesky) insert their own row at app boot.
|
|
95
|
+
|
|
96
|
+
INSERT INTO social_platforms (key, display_name, description, meta) VALUES
|
|
97
|
+
(
|
|
98
|
+
'linkedin',
|
|
99
|
+
'LinkedIn',
|
|
100
|
+
'Professional network. UGC Posts API (v2). Author URN required.',
|
|
101
|
+
jsonb_build_object('iconKey', 'linkedin', 'brandColor', '#0A66C2')
|
|
102
|
+
),
|
|
103
|
+
(
|
|
104
|
+
'x',
|
|
105
|
+
'X',
|
|
106
|
+
'Microblogging (formerly Twitter). v2 Tweets API. 280-char body.',
|
|
107
|
+
jsonb_build_object('iconKey', 'x', 'brandColor', '#000000')
|
|
108
|
+
),
|
|
109
|
+
(
|
|
110
|
+
'facebook',
|
|
111
|
+
'Facebook',
|
|
112
|
+
'Meta Graph API for Pages. Page access token required.',
|
|
113
|
+
jsonb_build_object('iconKey', 'facebook', 'brandColor', '#1877F2')
|
|
114
|
+
),
|
|
115
|
+
(
|
|
116
|
+
'instagram',
|
|
117
|
+
'Instagram',
|
|
118
|
+
'Meta Graph Content Publishing API (Business / Creator accounts).',
|
|
119
|
+
jsonb_build_object('iconKey', 'instagram', 'brandColor', '#E4405F')
|
|
120
|
+
)
|
|
121
|
+
ON CONFLICT (key) DO NOTHING;
|