@gravito/satellite-support 0.1.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # @gravito/satellite-support
2
+
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - @gravito/atlas@1.0.1
9
+ - @gravito/core@1.0.0
10
+ - @gravito/enterprise@1.0.0
11
+ - @gravito/ripple@1.0.0
@@ -0,0 +1,8 @@
1
+ import { ServiceProvider, Container } from '@gravito/core';
2
+
3
+ declare class SupportServiceProvider extends ServiceProvider {
4
+ register(_container: Container): void;
5
+ boot(): void;
6
+ }
7
+
8
+ export { SupportServiceProvider };
package/dist/index.js ADDED
@@ -0,0 +1,60 @@
1
+ // src/index.ts
2
+ import { ServiceProvider } from "@gravito/core";
3
+ var SupportServiceProvider = class extends ServiceProvider {
4
+ register(_container) {
5
+ }
6
+ boot() {
7
+ const core = this.core;
8
+ if (!core) {
9
+ return;
10
+ }
11
+ core.logger.info("\u{1F3A7} Support Satellite is ready for real-time inquiries");
12
+ const ripple = core.container.make("ripple.server");
13
+ ripple.on(
14
+ "support:client_message",
15
+ async (_socket, payload) => {
16
+ core.logger.info(`[Support] New message in ${payload.conversationId}`);
17
+ ripple.to(`private-support.chat.${payload.conversationId}`).emit("support:new_message", {
18
+ sender: "CUSTOMER",
19
+ text: payload.text,
20
+ at: /* @__PURE__ */ new Date()
21
+ });
22
+ ripple.to("presence-support.admin.inbox").emit("support:inbox_update", {
23
+ conversationId: payload.conversationId,
24
+ snippet: payload.text
25
+ });
26
+ }
27
+ );
28
+ core.router.prefix("/api/admin/v1/support").group((router) => {
29
+ router.get(
30
+ "/inbox",
31
+ (ctx) => ctx.json([
32
+ {
33
+ id: "SESS-001",
34
+ participant: "Carl",
35
+ contextType: "ORDER",
36
+ contextId: "ORD-9921",
37
+ status: "PENDING"
38
+ },
39
+ {
40
+ id: "SESS-002",
41
+ participant: "Guest_12",
42
+ contextType: "FORM",
43
+ subject: "\u7DB2\u7AD9\u5831\u50F9\u8A62\u554F",
44
+ status: "ACTIVE"
45
+ },
46
+ { id: "SESS-003", participant: "Alice", contextType: "GENERAL", status: "ACTIVE" }
47
+ ])
48
+ );
49
+ router.get(
50
+ "/conversations/:id/messages",
51
+ (ctx) => ctx.json([
52
+ { id: "msg-1", sender: "CUSTOMER", content: "\u60A8\u597D\uFF0C\u6211\u5C0D\u9019\u5F35\u8A02\u55AE\u6709\u7591\u554F", at: /* @__PURE__ */ new Date() }
53
+ ])
54
+ );
55
+ });
56
+ }
57
+ };
58
+ export {
59
+ SupportServiceProvider
60
+ };
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@gravito/satellite-support",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "build": "tsup src/index.ts --format esm --dts",
8
+ "typecheck": "bun tsc -p tsconfig.json --noEmit --skipLibCheck"
9
+ },
10
+ "types": "dist/index.d.ts",
11
+ "dependencies": {
12
+ "@gravito/enterprise": "workspace:*",
13
+ "@gravito/atlas": "workspace:*",
14
+ "@gravito/ripple": "workspace:*",
15
+ "@gravito/core": "workspace:*"
16
+ },
17
+ "devDependencies": {
18
+ "tsup": "^8.3.5",
19
+ "typescript": "^5.9.3"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/gravito-framework/gravito.git",
27
+ "directory": "satellites/support"
28
+ }
29
+ }
@@ -0,0 +1,10 @@
1
+ import type { Conversation } from '../Entities/Conversation'
2
+ import type { Message } from '../Entities/Message'
3
+
4
+ export interface ISupportRepository {
5
+ saveConversation(conversation: Conversation): Promise<void>
6
+ saveMessage(message: Message): Promise<void>
7
+ findConversationById(id: string): Promise<Conversation | null>
8
+ findMessagesByConversationId(conversationId: string): Promise<Message[]>
9
+ findPendingConversations(): Promise<Conversation[]>
10
+ }
@@ -0,0 +1,41 @@
1
+ import { Entity } from '@gravito/enterprise'
2
+
3
+ export type ConversationContextType = 'ORDER' | 'PRODUCT' | 'FORM' | 'GENERAL'
4
+
5
+ export interface ConversationProps {
6
+ participantId: string // 顧客 ID
7
+ subject?: string // 主旨 (如:來自聯繫表單)
8
+ contextType: ConversationContextType
9
+ contextId?: string // 關聯 ID (如:OrderId)
10
+ status: 'PENDING' | 'ACTIVE' | 'CLOSED'
11
+ lastMessageAt: Date
12
+ createdAt: Date
13
+ }
14
+
15
+ export class Conversation extends Entity<string> {
16
+ private _props: ConversationProps
17
+
18
+ constructor(props: ConversationProps, id?: string) {
19
+ super(id || crypto.randomUUID())
20
+ this._props = props
21
+ }
22
+
23
+ static start(
24
+ participantId: string,
25
+ context: { type: ConversationContextType; id?: string; subject?: string }
26
+ ) {
27
+ return new Conversation({
28
+ participantId,
29
+ subject: context.subject,
30
+ contextType: context.type,
31
+ contextId: context.id,
32
+ status: 'PENDING',
33
+ lastMessageAt: new Date(),
34
+ createdAt: new Date(),
35
+ })
36
+ }
37
+
38
+ unpack() {
39
+ return { ...this._props }
40
+ }
41
+ }
@@ -0,0 +1,30 @@
1
+ import { Entity } from '@gravito/enterprise'
2
+
3
+ export interface MessageProps {
4
+ conversationId: string
5
+ sender: 'CUSTOMER' | 'SUPPORT'
6
+ type: 'TEXT' | 'IMAGE' | 'CARD'
7
+ content: string
8
+ metadata?: any // 儲存結構化數據 (如:表單欄位、訂單卡片詳情)
9
+ createdAt: Date
10
+ }
11
+
12
+ export class Message extends Entity<string> {
13
+ private _props: MessageProps
14
+
15
+ constructor(props: MessageProps, id?: string) {
16
+ super(id || crypto.randomUUID())
17
+ this._props = props
18
+ }
19
+
20
+ static create(props: Omit<MessageProps, 'createdAt'>) {
21
+ return new Message({
22
+ ...props,
23
+ createdAt: new Date(),
24
+ })
25
+ }
26
+
27
+ unpack() {
28
+ return { ...this._props }
29
+ }
30
+ }
package/src/index.ts ADDED
@@ -0,0 +1,72 @@
1
+ import { type Container, ServiceProvider } from '@gravito/core'
2
+ import type { RippleServer } from '@gravito/ripple'
3
+
4
+ export class SupportServiceProvider extends ServiceProvider {
5
+ register(_container: Container): void {
6
+ // 注入 Repository 與 Use Cases (略,暫時使用 Mock)
7
+ }
8
+
9
+ override boot(): void {
10
+ const core = this.core
11
+ if (!core) {
12
+ return
13
+ }
14
+
15
+ core.logger.info('🎧 Support Satellite is ready for real-time inquiries')
16
+
17
+ // 獲取 Ripple 實體
18
+ const ripple = core.container.make<RippleServer>('ripple.server')
19
+
20
+ /**
21
+ * 監聽即時通訊事件
22
+ */
23
+ ripple.on(
24
+ 'support:client_message',
25
+ async (_socket, payload: { conversationId: string; text: string }) => {
26
+ core.logger.info(`[Support] New message in ${payload.conversationId}`)
27
+
28
+ // 廣播至該對話專屬頻道 (讓客服端即時收到)
29
+ ripple.to(`private-support.chat.${payload.conversationId}`).emit('support:new_message', {
30
+ sender: 'CUSTOMER',
31
+ text: payload.text,
32
+ at: new Date(),
33
+ })
34
+
35
+ // 同時通知管理員總收件匣
36
+ ripple.to('presence-support.admin.inbox').emit('support:inbox_update', {
37
+ conversationId: payload.conversationId,
38
+ snippet: payload.text,
39
+ })
40
+ }
41
+ )
42
+
43
+ // 註冊管理路由
44
+ core.router.prefix('/api/admin/v1/support').group((router) => {
45
+ router.get('/inbox', (ctx) =>
46
+ ctx.json([
47
+ {
48
+ id: 'SESS-001',
49
+ participant: 'Carl',
50
+ contextType: 'ORDER',
51
+ contextId: 'ORD-9921',
52
+ status: 'PENDING',
53
+ },
54
+ {
55
+ id: 'SESS-002',
56
+ participant: 'Guest_12',
57
+ contextType: 'FORM',
58
+ subject: '網站報價詢問',
59
+ status: 'ACTIVE',
60
+ },
61
+ { id: 'SESS-003', participant: 'Alice', contextType: 'GENERAL', status: 'ACTIVE' },
62
+ ])
63
+ )
64
+
65
+ router.get('/conversations/:id/messages', (ctx) =>
66
+ ctx.json([
67
+ { id: 'msg-1', sender: 'CUSTOMER', content: '您好,我對這張訂單有疑問', at: new Date() },
68
+ ])
69
+ )
70
+ })
71
+ }
72
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "baseUrl": ".",
6
+ "skipLibCheck": true,
7
+ "types": ["bun-types"],
8
+ "paths": {
9
+ "@gravito/core": ["../../packages/core/src/index.ts"],
10
+ "@gravito/*": ["../../packages/*/src/index.ts"]
11
+ }
12
+ },
13
+ "include": [
14
+ "src/**/*"
15
+ ],
16
+ "exclude": [
17
+ "node_modules",
18
+ "dist",
19
+ "**/*.test.ts"
20
+ ]
21
+ }