20251224
This commit is contained in:
parent
1259ba76c9
commit
6454e1b46b
19 changed files with 667 additions and 611 deletions
23
.gitignore
vendored
23
.gitignore
vendored
|
|
@ -1,23 +1,10 @@
|
||||||
handle-company-webhook/
|
|
||||||
|
|
||||||
terraform.*
|
terraform.*
|
||||||
.terraform*
|
.terraform*
|
||||||
|
|
||||||
IAM/
|
|
||||||
|
|
||||||
test/
|
|
||||||
|
|
||||||
venv/
|
|
||||||
__pycache__/
|
|
||||||
*.csv
|
|
||||||
|
|
||||||
request.json
|
|
||||||
|
|
||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
.env_dev
|
.env*
|
||||||
.env
|
credentials*
|
||||||
.env_prod
|
package-lock.json
|
||||||
credentials.json
|
*.sh
|
||||||
credentials_dev.json
|
log/
|
||||||
package-lock.json
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# APIエンドポイントURL
|
|
||||||
API_URL="https://sales-tool-gw-dev-ex1cujb.an.gateway.dev/trigger-minutes-workflow-from-miitel"
|
|
||||||
|
|
||||||
# APIキー(ヘッダーに付与する場合)
|
|
||||||
API_KEY="AIzaSyBVJOtvJTB4noAfUGEyMhCRqsF5yfypENc"
|
|
||||||
|
|
||||||
# リクエストボディ
|
|
||||||
JSON_FILE="request.json"
|
|
||||||
|
|
||||||
# curlコマンド実行
|
|
||||||
curl -X POST "$API_URL" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-H "x-api-key: $API_KEY" \
|
|
||||||
-d @"$JSON_FILE"
|
|
||||||
|
|
@ -5,10 +5,6 @@ info:
|
||||||
version: '1.0.0'
|
version: '1.0.0'
|
||||||
schemes:
|
schemes:
|
||||||
- 'https'
|
- 'https'
|
||||||
host: 'crate-minutes-gw-a8slsa47.an.gateway.dev'
|
|
||||||
x-google-endpoints:
|
|
||||||
- name: 'crate-minutes-gw-a8slsa47.an.gateway.dev'
|
|
||||||
allowCors: True
|
|
||||||
paths:
|
paths:
|
||||||
/create-minutes:
|
/create-minutes:
|
||||||
post:
|
post:
|
||||||
|
|
@ -70,6 +66,321 @@ paths:
|
||||||
Access-Control-Allow-Headers:
|
Access-Control-Allow-Headers:
|
||||||
type: string
|
type: string
|
||||||
default: 'Content-Type, x-api-key'
|
default: 'Content-Type, x-api-key'
|
||||||
|
|
||||||
|
/miitel:
|
||||||
|
post:
|
||||||
|
description: 'Miitel Webhook Processer'
|
||||||
|
operationId: 'miitel'
|
||||||
|
x-google-backend:
|
||||||
|
address: https://asia-northeast1-rational-timing-443808-u0.cloudfunctions.net/generate_minutes/api/miitel
|
||||||
|
path_translation: CONSTANT_ADDRESS
|
||||||
|
jwt_audience: https://asia-northeast1-rational-timing-443808-u0.cloudfunctions.net/generate_minutes
|
||||||
|
deadline: 600
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: JSON payload
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 'OK'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
401:
|
||||||
|
description: 'Auth Error'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
400:
|
||||||
|
description: 'Error'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
500:
|
||||||
|
description: 'Error'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
security:
|
||||||
|
- APIKeyHeader: []
|
||||||
|
|
||||||
|
/dailyBatch:
|
||||||
|
post:
|
||||||
|
description: 'get companies and owners'
|
||||||
|
operationId: 'dailyBatch'
|
||||||
|
x-google-backend:
|
||||||
|
address: https://asia-northeast1-rational-timing-443808-u0.cloudfunctions.net/generate_minutes/api/dailyBatch
|
||||||
|
path_translation: CONSTANT_ADDRESS
|
||||||
|
jwt_audience: https://asia-northeast1-rational-timing-443808-u0.cloudfunctions.net/generate_minutes
|
||||||
|
deadline: 600
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: JSON payload
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 'OK'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
401:
|
||||||
|
description: 'Auth Error'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
400:
|
||||||
|
description: 'Error'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
500:
|
||||||
|
description: 'Error'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
security:
|
||||||
|
- APIKeyHeader: []
|
||||||
|
|
||||||
|
/getLog:
|
||||||
|
post:
|
||||||
|
description: 'get log'
|
||||||
|
operationId: 'getLog'
|
||||||
|
x-google-backend:
|
||||||
|
address: https://asia-northeast1-rational-timing-443808-u0.cloudfunctions.net/generate_minutes/api/getLog
|
||||||
|
path_translation: CONSTANT_ADDRESS
|
||||||
|
jwt_audience: https://asia-northeast1-rational-timing-443808-u0.cloudfunctions.net/generate_minutes
|
||||||
|
deadline: 600
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: JSON payload
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 'OK'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
401:
|
||||||
|
description: 'Auth Error'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
400:
|
||||||
|
description: 'Error'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
500:
|
||||||
|
description: 'Error'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
security:
|
||||||
|
- APIKeyHeader: []
|
||||||
|
|
||||||
|
/reExecute:
|
||||||
|
post:
|
||||||
|
description: ''
|
||||||
|
operationId: 'reExecute'
|
||||||
|
x-google-backend:
|
||||||
|
address: https://asia-northeast1-rational-timing-443808-u0.cloudfunctions.net/generate_minutes/api/reExecute
|
||||||
|
path_translation: CONSTANT_ADDRESS
|
||||||
|
jwt_audience: https://asia-northeast1-rational-timing-443808-u0.cloudfunctions.net/generate_minutes
|
||||||
|
deadline: 600
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: JSON payload
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 'OK'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
401:
|
||||||
|
description: 'Auth Error'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
400:
|
||||||
|
description: 'Error'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
500:
|
||||||
|
description: 'Error'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
security:
|
||||||
|
- APIKeyHeader: []
|
||||||
|
|
||||||
|
/test:
|
||||||
|
post:
|
||||||
|
description: 'test'
|
||||||
|
operationId: 'test'
|
||||||
|
x-google-backend:
|
||||||
|
address: https://asia-northeast1-rational-timing-443808-u0.cloudfunctions.net/generate_minutes/api/test
|
||||||
|
path_translation: CONSTANT_ADDRESS
|
||||||
|
jwt_audience: https://asia-northeast1-rational-timing-443808-u0.cloudfunctions.net/generate_minutes
|
||||||
|
deadline: 600
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: JSON payload
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 'OK'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
401:
|
||||||
|
description: 'Auth Error'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
400:
|
||||||
|
description: 'Error'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
500:
|
||||||
|
description: 'Error'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
/alertTest:
|
||||||
|
post:
|
||||||
|
description: 'alertTest'
|
||||||
|
operationId: 'alertTest'
|
||||||
|
x-google-backend:
|
||||||
|
address: https://asia-northeast1-rational-timing-443808-u0.cloudfunctions.net/generate_minutes/api/alertTest
|
||||||
|
path_translation: CONSTANT_ADDRESS
|
||||||
|
jwt_audience: https://asia-northeast1-rational-timing-443808-u0.cloudfunctions.net/generate_minutes
|
||||||
|
deadline: 600
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
description: JSON payload
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 'OK'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
401:
|
||||||
|
description: 'Auth Error'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
500:
|
||||||
|
description: 'Error'
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
security:
|
||||||
|
- APIKeyHeader: []
|
||||||
|
options:
|
||||||
|
summary: 'CORS support'
|
||||||
|
operationId: 'test-options'
|
||||||
|
responses:
|
||||||
|
204:
|
||||||
|
description: 'CORS preflight'
|
||||||
|
headers:
|
||||||
|
Access-Control-Allow-Origin:
|
||||||
|
type: string
|
||||||
|
default: '*'
|
||||||
|
Access-Control-Allow-Methods:
|
||||||
|
type: string
|
||||||
|
default: 'GET, POST, OPTIONS'
|
||||||
|
Access-Control-Allow-Headers:
|
||||||
|
type: string
|
||||||
|
default: 'Content-Type, x-api-key'
|
||||||
|
|
||||||
securityDefinitions:
|
securityDefinitions:
|
||||||
APIKeyHeader:
|
APIKeyHeader:
|
||||||
type: apiKey
|
type: apiKey
|
||||||
|
|
|
||||||
|
|
@ -1,196 +0,0 @@
|
||||||
substitutions:
|
|
||||||
_ENV: 'dev'
|
|
||||||
_CF_SERVICE_ACCOUNT: 'mrt-cloudfunctions-sa-devtest'
|
|
||||||
_CW_SERVICE_ACCOUNT: 'mrt-cloudworkflows-sa-devtest'
|
|
||||||
|
|
||||||
options:
|
|
||||||
logging: CLOUD_LOGGING_ONLY
|
|
||||||
|
|
||||||
steps:
|
|
||||||
# 会社一覧取得
|
|
||||||
- id: 'gcloud functions deploy mrt-export-companies-to-gcs'
|
|
||||||
name: gcr.io/cloud-builders/gcloud
|
|
||||||
dir: 'functions/export-companies-to-gcs'
|
|
||||||
args: [
|
|
||||||
'functions',
|
|
||||||
'deploy',
|
|
||||||
'mrt-export-companies-to-gcs',
|
|
||||||
'--gen2',
|
|
||||||
'--runtime=python312',
|
|
||||||
'--region=asia-northeast1',
|
|
||||||
'--source=./source', # dir で切り替えているので「.」
|
|
||||||
'--entry-point=handle_request', # 変更する場合はここ
|
|
||||||
'--trigger-http',
|
|
||||||
'--allow-unauthenticated',
|
|
||||||
'--service-account=$_CF_SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com',
|
|
||||||
'--env-vars-file=.env_dev',
|
|
||||||
'--project=$PROJECT_ID',
|
|
||||||
'--quiet',
|
|
||||||
]
|
|
||||||
waitFor: ['-']
|
|
||||||
|
|
||||||
# 担当者一覧取得
|
|
||||||
- name: gcr.io/cloud-builders/gcloud
|
|
||||||
dir: 'functions/export-owners-to-gcs'
|
|
||||||
args: [
|
|
||||||
'functions',
|
|
||||||
'deploy',
|
|
||||||
'mrt-export-owners-to-gcs',
|
|
||||||
'--gen2',
|
|
||||||
'--runtime=python312',
|
|
||||||
'--region=asia-northeast1',
|
|
||||||
'--source=./source', # dir で切り替えているので「.」
|
|
||||||
'--entry-point=handle_request', # 変更する場合はここ
|
|
||||||
'--trigger-http',
|
|
||||||
'--no-allow-unauthenticated',
|
|
||||||
'--service-account=$_CF_SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com',
|
|
||||||
'--env-vars-file=.env_dev',
|
|
||||||
'--project=$PROJECT_ID',
|
|
||||||
'--quiet',
|
|
||||||
]
|
|
||||||
waitFor: ['-']
|
|
||||||
|
|
||||||
# スプレッドシート作成
|
|
||||||
- name: gcr.io/cloud-builders/gcloud
|
|
||||||
dir: 'functions/create-log-sheet'
|
|
||||||
args: [
|
|
||||||
'functions',
|
|
||||||
'deploy',
|
|
||||||
'mrt-create-log-sheet',
|
|
||||||
'--gen2',
|
|
||||||
'--runtime=python312',
|
|
||||||
'--region=asia-northeast1',
|
|
||||||
'--source=./source', # dir で切り替えているので「.」
|
|
||||||
'--entry-point=handle_request', # 変更する場合はここ
|
|
||||||
'--trigger-http',
|
|
||||||
'--no-allow-unauthenticated',
|
|
||||||
'--service-account=$_CF_SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com',
|
|
||||||
'--env-vars-file=.env_dev',
|
|
||||||
'--project=$PROJECT_ID',
|
|
||||||
'--quiet',
|
|
||||||
]
|
|
||||||
waitFor: ['-']
|
|
||||||
|
|
||||||
# ワークフロー呼び出し関数
|
|
||||||
- name: gcr.io/cloud-builders/gcloud
|
|
||||||
dir: 'functions/trigger-minutes-workflow-from-miitel'
|
|
||||||
args: [
|
|
||||||
'functions',
|
|
||||||
'deploy',
|
|
||||||
'mrt-trigger-minutes-workflow-from-miitel',
|
|
||||||
'--gen2',
|
|
||||||
'--runtime=python312',
|
|
||||||
'--region=asia-northeast1',
|
|
||||||
'--source=./source', # dir で切り替えているので「.」
|
|
||||||
'--entry-point=handle_request', # 変更する場合はここ
|
|
||||||
'--trigger-http',
|
|
||||||
'--no-allow-unauthenticated',
|
|
||||||
'--service-account=$_CF_SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com',
|
|
||||||
'--env-vars-file=.env_dev',
|
|
||||||
'--project=$PROJECT_ID',
|
|
||||||
'--quiet',
|
|
||||||
]
|
|
||||||
waitFor: ['-']
|
|
||||||
|
|
||||||
# 議事録作成関数
|
|
||||||
- name: gcr.io/cloud-builders/gcloud
|
|
||||||
dir: 'functions/generate-meeting-minutes'
|
|
||||||
args: [
|
|
||||||
'functions',
|
|
||||||
'deploy',
|
|
||||||
'mrt-generate-meeting-minutes',
|
|
||||||
'--gen2',
|
|
||||||
'--runtime=python312',
|
|
||||||
'--region=asia-northeast1',
|
|
||||||
'--source=./source', # dir で切り替えているので「.」
|
|
||||||
'--entry-point=handle_request', # 変更する場合はここ
|
|
||||||
'--trigger-http',
|
|
||||||
'--cpu=0.5',
|
|
||||||
'--memory=1Gi',
|
|
||||||
'--no-allow-unauthenticated',
|
|
||||||
'--service-account=$_CF_SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com',
|
|
||||||
'--env-vars-file=.env_dev',
|
|
||||||
'--project=$PROJECT_ID',
|
|
||||||
'--timeout=10m',
|
|
||||||
'--quiet',
|
|
||||||
]
|
|
||||||
waitFor: ['-']
|
|
||||||
|
|
||||||
# 議事録をドライブへアップロードする関数
|
|
||||||
- name: gcr.io/cloud-builders/gcloud
|
|
||||||
dir: 'functions/upload-minutes-to-drive'
|
|
||||||
args: [
|
|
||||||
'functions',
|
|
||||||
'deploy',
|
|
||||||
'mrt-upload-minutes-to-drive',
|
|
||||||
'--gen2',
|
|
||||||
'--runtime=python312',
|
|
||||||
'--region=asia-northeast1',
|
|
||||||
'--source=./source', # dir で切り替えているので「.」
|
|
||||||
'--entry-point=handle_request', # 変更する場合はここ
|
|
||||||
'--trigger-http',
|
|
||||||
'--cpu=0.5',
|
|
||||||
'--memory=1Gi',
|
|
||||||
'--no-allow-unauthenticated',
|
|
||||||
'--service-account=$_CF_SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com',
|
|
||||||
'--env-vars-file=.env_dev',
|
|
||||||
'--project=$PROJECT_ID',
|
|
||||||
'--quiet',
|
|
||||||
]
|
|
||||||
waitFor: ['-']
|
|
||||||
|
|
||||||
# Hubspot連携関数
|
|
||||||
- name: gcr.io/cloud-builders/gcloud
|
|
||||||
dir: 'functions/create-hubspot-meeting-log'
|
|
||||||
args: [
|
|
||||||
'functions',
|
|
||||||
'deploy',
|
|
||||||
'mrt-create-hubspot-meeting-log',
|
|
||||||
'--gen2',
|
|
||||||
'--runtime=python312',
|
|
||||||
'--region=asia-northeast1',
|
|
||||||
'--source=./source', # dir で切り替えているので「.」
|
|
||||||
'--entry-point=handle_request', # 変更する場合はここ
|
|
||||||
'--trigger-http',
|
|
||||||
'--no-allow-unauthenticated',
|
|
||||||
'--service-account=$_CF_SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com',
|
|
||||||
'--env-vars-file=.env_dev',
|
|
||||||
'--project=$PROJECT_ID',
|
|
||||||
'--quiet',
|
|
||||||
]
|
|
||||||
waitFor: ['-']
|
|
||||||
|
|
||||||
# スプレッドシートへ記録
|
|
||||||
- name: gcr.io/cloud-builders/gcloud
|
|
||||||
dir: 'functions/append-log-to-sheet'
|
|
||||||
args: [
|
|
||||||
'functions',
|
|
||||||
'deploy',
|
|
||||||
'mrt-append-log-to-sheet',
|
|
||||||
'--gen2',
|
|
||||||
'--runtime=python312',
|
|
||||||
'--region=asia-northeast1',
|
|
||||||
'--source=./source', # dir で切り替えているので「.」
|
|
||||||
'--entry-point=handle_request', # 変更する場合はここ
|
|
||||||
'--trigger-http',
|
|
||||||
'--no-allow-unauthenticated',
|
|
||||||
'--service-account=$_CF_SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com',
|
|
||||||
'--env-vars-file=.env_dev',
|
|
||||||
'--project=$PROJECT_ID',
|
|
||||||
'--quiet',
|
|
||||||
]
|
|
||||||
waitFor: ['-']
|
|
||||||
|
|
||||||
# ワークフロー
|
|
||||||
- name: gcr.io/cloud-builders/gcloud
|
|
||||||
dir: 'workflows/workflow-create-minutes'
|
|
||||||
args:
|
|
||||||
[
|
|
||||||
'workflows',
|
|
||||||
'deploy',
|
|
||||||
'mrt-workflow-create-minutes',
|
|
||||||
'--location=asia-northeast1',
|
|
||||||
'--source=main.yaml',
|
|
||||||
'--service-account=$_CW_SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com',
|
|
||||||
'--quiet',
|
|
||||||
]
|
|
||||||
|
|
@ -1,193 +0,0 @@
|
||||||
substitutions:
|
|
||||||
_ENV: 'prod'
|
|
||||||
_CF_SERVICE_ACCOUNT: 'mrt-cloudfunctions-sa'
|
|
||||||
_CW_SERVICE_ACCOUNT: 'mrt-cloudworkflows-sa'
|
|
||||||
|
|
||||||
steps:
|
|
||||||
# 会社一覧取得
|
|
||||||
- id: 'gcloud functions deploy mrt-export-companies-to-gcs'
|
|
||||||
name: gcr.io/cloud-builders/gcloud
|
|
||||||
dir: 'functions/export-companies-to-gcs'
|
|
||||||
args: [
|
|
||||||
'functions',
|
|
||||||
'deploy',
|
|
||||||
'mrt-export-companies-to-gcs',
|
|
||||||
'--gen2',
|
|
||||||
'--runtime=python312',
|
|
||||||
'--region=asia-northeast1',
|
|
||||||
'--source=./source', # dir で切り替えているので「.」
|
|
||||||
'--entry-point=handle_request', # 変更する場合はここ
|
|
||||||
'--trigger-http',
|
|
||||||
'--allow-unauthenticated',
|
|
||||||
'--service-account=$_CF_SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com',
|
|
||||||
'--env-vars-file=.env_prod',
|
|
||||||
'--project=$PROJECT_ID',
|
|
||||||
'--quiet',
|
|
||||||
]
|
|
||||||
waitFor: ['-']
|
|
||||||
|
|
||||||
# 担当者一覧取得
|
|
||||||
- name: gcr.io/cloud-builders/gcloud
|
|
||||||
dir: 'functions/export-owners-to-gcs'
|
|
||||||
args: [
|
|
||||||
'functions',
|
|
||||||
'deploy',
|
|
||||||
'mrt-export-owners-to-gcs',
|
|
||||||
'--gen2',
|
|
||||||
'--runtime=python312',
|
|
||||||
'--region=asia-northeast1',
|
|
||||||
'--source=./source', # dir で切り替えているので「.」
|
|
||||||
'--entry-point=handle_request', # 変更する場合はここ
|
|
||||||
'--trigger-http',
|
|
||||||
'--no-allow-unauthenticated',
|
|
||||||
'--service-account=$_CF_SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com',
|
|
||||||
'--env-vars-file=.env_prod',
|
|
||||||
'--project=$PROJECT_ID',
|
|
||||||
'--quiet',
|
|
||||||
]
|
|
||||||
waitFor: ['-']
|
|
||||||
|
|
||||||
# スプレッドシート作成
|
|
||||||
- name: gcr.io/cloud-builders/gcloud
|
|
||||||
dir: 'functions/create-log-sheet'
|
|
||||||
args: [
|
|
||||||
'functions',
|
|
||||||
'deploy',
|
|
||||||
'mrt-create-log-sheet',
|
|
||||||
'--gen2',
|
|
||||||
'--runtime=python312',
|
|
||||||
'--region=asia-northeast1',
|
|
||||||
'--source=./source', # dir で切り替えているので「.」
|
|
||||||
'--entry-point=handle_request', # 変更する場合はここ
|
|
||||||
'--trigger-http',
|
|
||||||
'--no-allow-unauthenticated',
|
|
||||||
'--service-account=$_CF_SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com',
|
|
||||||
'--env-vars-file=.env_prod',
|
|
||||||
'--project=$PROJECT_ID',
|
|
||||||
'--quiet',
|
|
||||||
]
|
|
||||||
waitFor: ['-']
|
|
||||||
|
|
||||||
# ワークフロー呼び出し関数
|
|
||||||
- name: gcr.io/cloud-builders/gcloud
|
|
||||||
dir: 'functions/trigger-minutes-workflow-from-miitel'
|
|
||||||
args: [
|
|
||||||
'functions',
|
|
||||||
'deploy',
|
|
||||||
'mrt-trigger-minutes-workflow-from-miitel',
|
|
||||||
'--gen2',
|
|
||||||
'--runtime=python312',
|
|
||||||
'--region=asia-northeast1',
|
|
||||||
'--source=./source', # dir で切り替えているので「.」
|
|
||||||
'--entry-point=handle_request', # 変更する場合はここ
|
|
||||||
'--trigger-http',
|
|
||||||
'--no-allow-unauthenticated',
|
|
||||||
'--service-account=$_CF_SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com',
|
|
||||||
'--env-vars-file=.env_prod',
|
|
||||||
'--project=$PROJECT_ID',
|
|
||||||
'--quiet',
|
|
||||||
]
|
|
||||||
waitFor: ['-']
|
|
||||||
|
|
||||||
# 議事録作成関数
|
|
||||||
- name: gcr.io/cloud-builders/gcloud
|
|
||||||
dir: 'functions/generate-meeting-minutes'
|
|
||||||
args: [
|
|
||||||
'functions',
|
|
||||||
'deploy',
|
|
||||||
'mrt-generate-meeting-minutes',
|
|
||||||
'--gen2',
|
|
||||||
'--runtime=python312',
|
|
||||||
'--region=asia-northeast1',
|
|
||||||
'--source=./source', # dir で切り替えているので「.」
|
|
||||||
'--entry-point=handle_request', # 変更する場合はここ
|
|
||||||
'--trigger-http',
|
|
||||||
'--cpu=0.5',
|
|
||||||
'--memory=1Gi',
|
|
||||||
'--no-allow-unauthenticated',
|
|
||||||
'--service-account=$_CF_SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com',
|
|
||||||
'--env-vars-file=.env_prod',
|
|
||||||
'--project=$PROJECT_ID',
|
|
||||||
'--timeout=10m',
|
|
||||||
'--quiet',
|
|
||||||
]
|
|
||||||
waitFor: ['-']
|
|
||||||
|
|
||||||
# 議事録をドライブへアップロードする関数
|
|
||||||
- name: gcr.io/cloud-builders/gcloud
|
|
||||||
dir: 'functions/upload-minutes-to-drive'
|
|
||||||
args: [
|
|
||||||
'functions',
|
|
||||||
'deploy',
|
|
||||||
'mrt-upload-minutes-to-drive',
|
|
||||||
'--gen2',
|
|
||||||
'--runtime=python312',
|
|
||||||
'--region=asia-northeast1',
|
|
||||||
'--source=./source', # dir で切り替えているので「.」
|
|
||||||
'--entry-point=handle_request', # 変更する場合はここ
|
|
||||||
'--trigger-http',
|
|
||||||
'--cpu=0.5',
|
|
||||||
'--memory=1Gi',
|
|
||||||
'--no-allow-unauthenticated',
|
|
||||||
'--service-account=$_CF_SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com',
|
|
||||||
'--env-vars-file=.env_prod',
|
|
||||||
'--project=$PROJECT_ID',
|
|
||||||
'--quiet',
|
|
||||||
]
|
|
||||||
waitFor: ['-']
|
|
||||||
|
|
||||||
# Hubspot連携関数
|
|
||||||
- name: gcr.io/cloud-builders/gcloud
|
|
||||||
dir: 'functions/create-hubspot-meeting-log'
|
|
||||||
args: [
|
|
||||||
'functions',
|
|
||||||
'deploy',
|
|
||||||
'mrt-create-hubspot-meeting-log',
|
|
||||||
'--gen2',
|
|
||||||
'--runtime=python312',
|
|
||||||
'--region=asia-northeast1',
|
|
||||||
'--source=./source', # dir で切り替えているので「.」
|
|
||||||
'--entry-point=handle_request', # 変更する場合はここ
|
|
||||||
'--trigger-http',
|
|
||||||
'--no-allow-unauthenticated',
|
|
||||||
'--service-account=$_CF_SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com',
|
|
||||||
'--env-vars-file=.env_prod',
|
|
||||||
'--project=$PROJECT_ID',
|
|
||||||
'--quiet',
|
|
||||||
]
|
|
||||||
waitFor: ['-']
|
|
||||||
|
|
||||||
# スプレッドシートへ記録
|
|
||||||
- name: gcr.io/cloud-builders/gcloud
|
|
||||||
dir: 'functions/append-log-to-sheet'
|
|
||||||
args: [
|
|
||||||
'functions',
|
|
||||||
'deploy',
|
|
||||||
'mrt-append-log-to-sheet',
|
|
||||||
'--gen2',
|
|
||||||
'--runtime=python312',
|
|
||||||
'--region=asia-northeast1',
|
|
||||||
'--source=./source', # dir で切り替えているので「.」
|
|
||||||
'--entry-point=handle_request', # 変更する場合はここ
|
|
||||||
'--trigger-http',
|
|
||||||
'--no-allow-unauthenticated',
|
|
||||||
'--service-account=$_CF_SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com',
|
|
||||||
'--env-vars-file=.env_prod',
|
|
||||||
'--project=$PROJECT_ID',
|
|
||||||
'--quiet',
|
|
||||||
]
|
|
||||||
waitFor: ['-']
|
|
||||||
|
|
||||||
# ワークフロー
|
|
||||||
- name: gcr.io/cloud-builders/gcloud
|
|
||||||
dir: 'workflows/workflow-create-minutes'
|
|
||||||
args:
|
|
||||||
[
|
|
||||||
'workflows',
|
|
||||||
'deploy',
|
|
||||||
'mrt-workflow-create-minutes',
|
|
||||||
'--location=asia-northeast1',
|
|
||||||
'--source=main.yaml',
|
|
||||||
'--service-account=$_CW_SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com',
|
|
||||||
'--quiet',
|
|
||||||
]
|
|
||||||
|
|
@ -19,6 +19,9 @@ node_modules
|
||||||
.env_prod
|
.env_prod
|
||||||
|
|
||||||
deploy_function_dev.sh
|
deploy_function_dev.sh
|
||||||
|
deploy_function_prod.sh
|
||||||
|
|
||||||
|
files/
|
||||||
|
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"start": "npm run build && functions-framework --target=helloHttp --port=8080 --source=dist/index.js",
|
"start": "npm run build && functions-framework --target=helloHttp --port=8080 --source=dist/index.js",
|
||||||
"debug": "dotenv -e .env_dev -- node --inspect node_modules/.bin/functions-framework --source=dist/index.js --target=helloHttp",
|
"debug": "dotenv -e .env_prod -- node --inspect node_modules/.bin/functions-framework --source=dist/index.js --target=helloHttp",
|
||||||
"watch": "concurrently \"dotenv -e .env_dev -- npm run build -- --watch\" \"dotenv -e .env_dev -- nodemon --watch ./dist/ --exec npm run debug\""
|
"watch": "concurrently \"dotenv -e .env_prod -- npm run build -- --watch\" \"dotenv -e .env_prod -- nodemon --watch ./dist/ --exec npm run debug\""
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@google-cloud/functions-framework": "^3.0.0",
|
"@google-cloud/functions-framework": "^3.0.0",
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
"fast-fuzzy": "^1.12.0",
|
"fast-fuzzy": "^1.12.0",
|
||||||
"googleapis": "^105.0.0",
|
"googleapis": "^105.0.0",
|
||||||
|
"marked": "^17.0.1",
|
||||||
"zod": "^4.1.13"
|
"zod": "^4.1.13"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
export const GEMINI_MODEL_ID = "gemini-2.5-flash";
|
export const GEMINI_MODEL_ID = "gemini-2.5-pro";
|
||||||
export const DEBUG = true;
|
export const DEBUG = false;
|
||||||
|
|
||||||
export const CLOUD_STORAGE_MASTER_FOLDER_NAME = "master";
|
export const CLOUD_STORAGE_MASTER_FOLDER_NAME = "master";
|
||||||
export const CLOUD_STORAGE_LOG_FOLDER_NAME = "new_request_log";
|
export const CLOUD_STORAGE_LOG_FOLDER_NAME = "new_request_log";
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import zlib from "zlib";
|
import zlib from "zlib";
|
||||||
import { storageController } from "./logics/storage";
|
import { storageController } from "./logics/storage";
|
||||||
import { MiiTelWebhookSchema, processRequest } from "./logics/process";
|
import { logUploadProcess, MiiTelWebhookSchema, processRequest, testProcess } from "./logics/process";
|
||||||
import { hubspotController } from "./logics/hubspot";
|
import { hubspotController } from "./logics/hubspot";
|
||||||
import { createCustomError, responseError } from "./logics/error";
|
import { createCustomError, responseError } from "./logics/error";
|
||||||
import { CLOUD_STORAGE_LOG_FOLDER_NAME, CLOUD_STORAGE_MASTER_FOLDER_NAME, COMPANIES_FILE_NAME, OWNERS_FILE_NAME } from "../serverConfig";
|
import { CLOUD_STORAGE_LOG_FOLDER_NAME, CLOUD_STORAGE_MASTER_FOLDER_NAME, COMPANIES_FILE_NAME, OWNERS_FILE_NAME } from "../serverConfig";
|
||||||
import { Delay } from "cerceis-lib";
|
import { Delay } from "cerceis-lib";
|
||||||
import { googleDriveController } from "./logics/googleDrive";
|
import path from "path";
|
||||||
|
import fs from "fs";
|
||||||
|
import { fuzzyMatchController } from "./logics/fuzzyMatch";
|
||||||
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
|
@ -30,8 +33,7 @@ router.post("/miitel", async (req, res) => {
|
||||||
|
|
||||||
return res.status(200).send("ok");
|
return res.status(200).send("ok");
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
responseError(err, res);
|
return responseError(err, res);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -41,18 +43,19 @@ router.post("/dailyBatch", async (req, res) => {
|
||||||
console.log("Starting daily batch process...");
|
console.log("Starting daily batch process...");
|
||||||
// export companies to GCS
|
// export companies to GCS
|
||||||
const companies = await hubspotController.getCompanies();
|
const companies = await hubspotController.getCompanies();
|
||||||
if(!companies) throw createCustomError("GET_OWNERS_FAILED");
|
if(!companies) throw createCustomError("GET_COMPANIES_FAILED");
|
||||||
await storageController.saveToGCS(CLOUD_STORAGE_MASTER_FOLDER_NAME, COMPANIES_FILE_NAME, JSON.stringify(companies), 'application/json');
|
await storageController.saveToGCS(CLOUD_STORAGE_MASTER_FOLDER_NAME, COMPANIES_FILE_NAME, JSON.stringify(companies), 'application/json');
|
||||||
|
|
||||||
// export owners to GCS
|
// export owners to GCS
|
||||||
const owners = await hubspotController.getOwners();
|
const owners = await hubspotController.getOwners();
|
||||||
if(!owners) throw createCustomError("GET_COMPANIES_FAILED");
|
if(!owners) throw createCustomError("GET_OWNERS_FAILED");
|
||||||
await storageController.saveToGCS(CLOUD_STORAGE_MASTER_FOLDER_NAME, OWNERS_FILE_NAME, JSON.stringify(owners), 'application/json');
|
await storageController.saveToGCS(CLOUD_STORAGE_MASTER_FOLDER_NAME, OWNERS_FILE_NAME, JSON.stringify(owners), 'application/json');
|
||||||
|
|
||||||
res.status(200).send("Daily batch executed.");
|
res.status(200).send("Daily batch executed.");
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error in daily batch:", error);
|
console.error("Error in daily batch:", error);
|
||||||
|
return res.status(400).send("Error executing daily batch.");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -60,11 +63,13 @@ router.post("/dailyBatch", async (req, res) => {
|
||||||
router.post("/getLog", async (req, res) => {
|
router.post("/getLog", async (req, res) => {
|
||||||
console.log(req.body);
|
console.log(req.body);
|
||||||
const meetingId = req.body.meetingId;
|
const meetingId = req.body.meetingId;
|
||||||
const exist = await storageController.existsInGCS("request_log", "test.json.gz");
|
const exist = await storageController.existsInGCS(CLOUD_STORAGE_LOG_FOLDER_NAME, `${meetingId}.json.gz`);
|
||||||
console.log("Log exists:", exist);
|
console.log("Log exists:", exist);
|
||||||
const log = await storageController.loadFromGCS("request_log", meetingId + ".json.gz");
|
const log = await storageController.loadFromGCS(CLOUD_STORAGE_LOG_FOLDER_NAME, meetingId + ".json.gz");
|
||||||
console.log(log)
|
if(!log) throw Error();
|
||||||
res.send(log);
|
const params = MiiTelWebhookSchema.parse(JSON.parse(log));
|
||||||
|
// console.log(params)
|
||||||
|
res.send(params);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -77,11 +82,12 @@ router.post("/reExecute", async (req, res) => {
|
||||||
const log = await storageController.loadFromGCS(CLOUD_STORAGE_LOG_FOLDER_NAME, `${meetingId}.json.gz`);
|
const log = await storageController.loadFromGCS(CLOUD_STORAGE_LOG_FOLDER_NAME, `${meetingId}.json.gz`);
|
||||||
if(!log) throw Error();
|
if(!log) throw Error();
|
||||||
const params = MiiTelWebhookSchema.safeParse(JSON.parse(log));
|
const params = MiiTelWebhookSchema.safeParse(JSON.parse(log));
|
||||||
|
console.log(params);
|
||||||
if(!params.success) throw createCustomError("ZOD_FAILED");
|
if(!params.success) throw createCustomError("ZOD_FAILED");
|
||||||
params.data.video.title = newTitle;
|
params.data.video.title = newTitle;
|
||||||
// console.log(params.data.video)
|
// console.log(params.data.video)
|
||||||
|
|
||||||
await processRequest(params.data.video);
|
// await processRequest(params.data.video);
|
||||||
|
|
||||||
res.send(log);
|
res.send(log);
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
|
|
@ -92,28 +98,39 @@ router.post("/reExecute", async (req, res) => {
|
||||||
|
|
||||||
|
|
||||||
// 過去のログを全てGoogle Driveへアップロード
|
// 過去のログを全てGoogle Driveへアップロード
|
||||||
router.post("/logUpload", async (req, res) => {
|
// router.post("/logUpload", async (req, res) => {
|
||||||
try {
|
// try {
|
||||||
const list = await storageController.getFileList();
|
// const list = await storageController.getFileList();
|
||||||
if(!list) throw createCustomError("GET_FILES_FAILED");
|
// if(!list) throw createCustomError("GET_FILES_FAILED");
|
||||||
for(const l of list){
|
// console.log("Total files to process:", list.length);
|
||||||
console.log(l);
|
// const failedFiles: string[] = [];
|
||||||
const fileName = l.split('/')[1]
|
// let count = 0;
|
||||||
const log = await storageController.loadFromGCS('request_log', fileName);
|
// const tmplist = list.slice(1600,1800);
|
||||||
if(!log) throw createCustomError("GET_FILES_FAILED");
|
// for(const l of tmplist){
|
||||||
// console.log(log);
|
// console.log(l);
|
||||||
const parsedLog = MiiTelWebhookSchema.safeParse(JSON.parse(log));
|
// count++;
|
||||||
if(!parsedLog.success) throw createCustomError("ZOD_FAILED");
|
// console.log(`Processing file ${count} of ${tmplist.length}`);
|
||||||
console.log(parsedLog.data.video.title);
|
// const fileName = l.split('/')[1]
|
||||||
|
// const log = await storageController.loadFromGCS('request_log', fileName);
|
||||||
await Delay(500);
|
// if(!log) {
|
||||||
}
|
// failedFiles.push(fileName);
|
||||||
res.send('ok');
|
// continue;
|
||||||
} catch(error) {
|
// };
|
||||||
console.log(error);
|
// const parsedLog = MiiTelWebhookSchema.safeParse(JSON.parse(log));
|
||||||
res.status(400).send("Failed");
|
// if(!parsedLog.success) throw createCustomError("ZOD_FAILED");
|
||||||
}
|
// console.log(parsedLog.data.video.title);
|
||||||
});
|
// const result = await logUploadProcess(parsedLog.data.video);
|
||||||
|
// if(!result) failedFiles.push(fileName);
|
||||||
|
// await Delay(500);
|
||||||
|
// }
|
||||||
|
// const outputPath = path.join(__dirname, "../log/", 'failedFiles.json');
|
||||||
|
// fs.writeFileSync(outputPath, JSON.stringify(failedFiles, null, 2));
|
||||||
|
// res.send('ok');
|
||||||
|
// } catch(error) {
|
||||||
|
// console.log(error);
|
||||||
|
// res.status(400).send("Failed");
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
// router.post("/deleteFile", async (req, res) => {
|
// router.post("/deleteFile", async (req, res) => {
|
||||||
// console.log(req.body);
|
// console.log(req.body);
|
||||||
|
|
@ -126,20 +143,32 @@ router.post("/logUpload", async (req, res) => {
|
||||||
|
|
||||||
router.post("/test", async (req, res) => {
|
router.post("/test", async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
await testProcess();
|
||||||
// const googleAuth = await googleDriveController.getAuth();
|
|
||||||
// const driveClilent = googleDriveController.getDriveClient(googleAuth);
|
|
||||||
// const sheetsClient = googleDriveController.getSheetsClient(googleAuth);
|
|
||||||
// const folderId = await googleDriveController.searchFileIdByFileName(driveClilent, MINUTES_CREATION_HISTORY_FOLDER_ID, '2025');
|
|
||||||
// if(!folderId) throw new Error()
|
|
||||||
// console.log(fileId);
|
|
||||||
// const sheetId = await googleDriveController.getLogSheetId(driveClilent, sheetsClient, folderId, 'test1');
|
|
||||||
// console.log('sheet id : ', sheetId);
|
|
||||||
res.send("ok");
|
res.send("ok");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error in /test endpoint:", error);
|
console.error(error);
|
||||||
res.status(500).send("Error in /test endpoint");
|
res.status(400).send("Error in /test endpoint");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
router.post("/alertTest", async (_req, res) => {
|
||||||
|
res.status(500).send("Error");
|
||||||
|
});
|
||||||
|
|
||||||
|
// router.post("/debug", async (req, res) => {
|
||||||
|
// try {
|
||||||
|
// const a = await fuzzyMatchController.searchMatchedCompany("Aコープ九");
|
||||||
|
// console.log(a);
|
||||||
|
// res.send("ok");
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error(error);
|
||||||
|
// res.status(400).send("Error in /test endpoint");
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
@ -8,20 +8,91 @@ const aiClient = new GoogleGenAI({
|
||||||
export const aiController = {
|
export const aiController = {
|
||||||
generateMinutes: async(text: string): Promise<string | null> => {
|
generateMinutes: async(text: string): Promise<string | null> => {
|
||||||
const prompt = `
|
const prompt = `
|
||||||
あなたは議事録作成のプロフェッショナルです。以下の「文字起こし結果」は営業マンが録音した商談の文字起こしです。以下の制約条件に従い、最高の商談報告の議事録を作成してください。
|
あなたは、流通・小売・飲食業界向けにシステムを提供する「データコム株式会社」の優秀な営業アシスタントです。
|
||||||
|
以下の[文字起こし結果]をもとに、関係者(社内および顧客・パートナー)に共有するための、正確で可読性の高い議事録を作成してください。
|
||||||
|
|
||||||
制約条件:
|
# 前提条件
|
||||||
1. 文字起こし結果にはAIによる書き起こしミスがある可能性を考慮してください。
|
* **当社(データコム株式会社):** システム開発会社。店舗分析、DX、業務効率化システムなどを提案・提供する立場。
|
||||||
2. 冒頭に主要な「決定事項」と「アクションアイテム」をまとめてください。
|
* **相手:**
|
||||||
3. 議論のポイントを議題ごとに要約してください。
|
* パターンA(エンドユーザー): 小売業(スーパー等)や飲食業の経営層・現場担当者。現場の課題や予算について話す。
|
||||||
4. 見出しや箇条書きを用いて、情報が探しやすい構造で簡潔かつ明瞭に記述してください。
|
* パターンB(パートナー): システム会社、代理店、POSメーカー等。協業、API連携、紹介案件について話す。
|
||||||
5. 要約は500文字以内に収めてください。
|
* **入力データ:** 対面会議のスマホ録音が含まれるため、話者ラベル(Speaker A, B等)は不正確です。必ず「発言内容」から誰が話しているかを判断してください。
|
||||||
6. 箇条書き形式で簡潔にまとめてください。
|
|
||||||
7. マークダウン記法は使わず、各項目を「■」や「・」等を使って見やすくしてください。
|
|
||||||
|
|
||||||
文字起こし結果:
|
# 重要:専門用語・表記ルール(辞書)
|
||||||
${text}
|
文字起こし結果に誤字や、以下の「読み」に近い表現があった場合、必ず「正しい表記」に修正・統一してください。
|
||||||
`
|
|
||||||
|
| カテゴリ | 正しい表記 | 読み・備考 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **社名** | データコム | でーたこむ(当社) |
|
||||||
|
| **製品・サービス** | ID-POS | あいでぃーぽす |
|
||||||
|
| | ArmBox | あーむぼっくす |
|
||||||
|
| | RV | あーるぶい |
|
||||||
|
| | MS-View | えむえすびゅー |
|
||||||
|
| | AWS | えーだぶりゅーえす |
|
||||||
|
| | CustomerJournal (CJ) | かすたまーじゃーなる / しーじぇー |
|
||||||
|
| | Tiramisu | てぃらみす(お菓子ではなくシステム名) |
|
||||||
|
| | TerraMap | てらまっぷ |
|
||||||
|
| | d@Journal | でぃーあっと |
|
||||||
|
| | d3 | でぃーすりー |
|
||||||
|
| | D-PLAN | でぃーぷらん |
|
||||||
|
| | PV | ぴーぶい |
|
||||||
|
| | FreshO2 | ふれっしゅおーつー |
|
||||||
|
| | Point View | ぽいんとびゅー |
|
||||||
|
| | Retail View | りてーるびゅー |
|
||||||
|
| **一般・業界用語** | RFP | あーるえふぴー(提案依頼書) |
|
||||||
|
| | ジャーナルデータ | じゃーなるでーた |
|
||||||
|
| | 帳票 | ちょうひょう |
|
||||||
|
| | DWH | でぃーだぶりゅーえっち |
|
||||||
|
| | POS | ぽす |
|
||||||
|
| | CUBIC | きゅーびっく |
|
||||||
|
| | NOCC | のっく |
|
||||||
|
| **人名(当社関係者)**| 新垣、曽田、瀧本、田邊、會田 | しんがき、そだ、たきもと、たなべ、あいた |
|
||||||
|
| | 永倉、早坂、松浦、松永 | ながくら、はやさか、まつうら、まつなが |
|
||||||
|
|
||||||
|
# 思考・処理ステップ
|
||||||
|
1. **用語の補正:** 上記の辞書に基づき、製品名や人名の誤変換を脳内で修正する。(例:「てぃらみすが」→「Tiramisuが」)
|
||||||
|
2. **話者の特定:**
|
||||||
|
* 「システムの説明」「事例の紹介」「持ち帰って検討します(提案側として)」等の発言は「データコム(当社)」とみなす。
|
||||||
|
* 「現場のオペレーション」「予算感」「現状のシステムの不満」等の発言は「相手先」とみなす。
|
||||||
|
3. **会議タイプの判定:**
|
||||||
|
* 内容が導入検討・商談であれば「商談報告」モードで作成。
|
||||||
|
* 内容が仕様調整・協業・定例であれば「打合せ報告」モードで作成。
|
||||||
|
4. **要約と構成:** 単なる会話の羅列ではなく、ロジカルに構造化する。
|
||||||
|
|
||||||
|
# 出力フォーマット(マークダウン)
|
||||||
|
|
||||||
|
## 会議概要
|
||||||
|
* **会議タイプ:** (商談 / パートナー協議 / 定例 etc.)
|
||||||
|
* **相手先:** (文脈から推測できる会社名や属性。不明な場合は「顧客」)
|
||||||
|
* **参加者(推測):** (判別できた場合のみ記載。当社: 〇〇 / 相手: 〇〇)
|
||||||
|
* **要約:** (会議の全体像を300文字以内で簡潔に)
|
||||||
|
|
||||||
|
## 決定事項・合意事項
|
||||||
|
* (確定したアクション、合意した条件、次回の予定など)
|
||||||
|
* ...
|
||||||
|
|
||||||
|
## ネクストアクション(ToDo)
|
||||||
|
| 担当 | タスク内容 | 期限・備考 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| 当社 | ... | ... |
|
||||||
|
| 相手 | ... | ... |
|
||||||
|
|
||||||
|
## 議題詳細とポイント
|
||||||
|
### (議題1のタイトル)
|
||||||
|
* **現状・課題:** (相手が抱えている悩み、現状のシステム構成など)
|
||||||
|
* **当社提案・回答:** (データコム側が提示した解決策、機能説明)
|
||||||
|
* **反応:** (相手の感触、懸念点)
|
||||||
|
|
||||||
|
### (議題2のタイトル)
|
||||||
|
...
|
||||||
|
|
||||||
|
## 懸念点・確認事項
|
||||||
|
* (技術的なハードル、予算の壁、競合の存在など、リスク情報があれば記載)
|
||||||
|
|
||||||
|
---
|
||||||
|
[文字起こし結果]:
|
||||||
|
${text}
|
||||||
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await aiClient.models.generateContent({
|
const response = await aiClient.models.generateContent({
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,8 @@ import fs from "fs";
|
||||||
|
|
||||||
|
|
||||||
export const fileController = {
|
export const fileController = {
|
||||||
createMinutesFileName: (title: string, hostName: string, jstStartsAt: Date): string => {
|
createMinutesFileName: (title: string, hostName: string, meetingDateStr: string): string => {
|
||||||
const dateStr = dateController.getFormattedDate(jstStartsAt, "yyyy年MM月dd日");
|
const fileName = `${meetingDateStr} ${title.replace('/', '')} ${hostName}`;
|
||||||
const fileName = `${dateStr} ${title} ${hostName}`;
|
|
||||||
return fileName;
|
return fileName;
|
||||||
},
|
},
|
||||||
extractCompanyNameFromTitle: (title: string) => {
|
extractCompanyNameFromTitle: (title: string) => {
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,13 @@ export const fuzzyMatchController = {
|
||||||
if(!parsedCompanies.success) return null;
|
if(!parsedCompanies.success) return null;
|
||||||
|
|
||||||
const normalizedCompanyName = fuzzyMatchController.normalizeCompanyName(companyName);
|
const normalizedCompanyName = fuzzyMatchController.normalizeCompanyName(companyName);
|
||||||
const normalizedCompanies: Company[] = parsedCompanies.data.map((c) => CompanySchema.parse({
|
const companies: Company[] = parsedCompanies.data.map((c) => CompanySchema.parse({
|
||||||
id: c.id,
|
id: c.id,
|
||||||
name: fuzzyMatchController.normalizeCompanyName(c.name),
|
name: c.name,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Exact Match
|
// Exact Match
|
||||||
const exactMatchedCompany = fuzzyMatchController.searchExactMatchedCompany(normalizedCompanyName, normalizedCompanies);
|
const exactMatchedCompany = fuzzyMatchController.searchExactMatchedCompany(normalizedCompanyName, companies);
|
||||||
// console.log(exactMatchedCompanyId);
|
// console.log(exactMatchedCompanyId);
|
||||||
if(exactMatchedCompany) return exactMatchedCompany;
|
if(exactMatchedCompany) return exactMatchedCompany;
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ export const fuzzyMatchController = {
|
||||||
},
|
},
|
||||||
searchExactMatchedCompany: (companyName: string, companies: Company[]): Company | null => {
|
searchExactMatchedCompany: (companyName: string, companies: Company[]): Company | null => {
|
||||||
for(const company of companies) {
|
for(const company of companies) {
|
||||||
if(companyName === company.name) return company;
|
if(companyName === fuzzyMatchController.normalizeCompanyName(company.name)) return company;
|
||||||
};
|
};
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { docs_v1, drive_v3, google, sheets_v4 } from "googleapis";
|
import { docs_v1, drive_v3, google, sheets_v4 } from "googleapis";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { DEBUG, LOG_SHEET_HEADER_VALUES, SHEET_MIMETYPE } from "../../serverConfig";
|
import { DEBUG, DOCUMENT_MIMETYPE, LOG_SHEET_HEADER_VALUES, SHEET_MIMETYPE } from "../../serverConfig";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
import { Readable } from "stream";
|
||||||
|
|
||||||
const GOOGLE_DRIVE_FOLDER_ID = process.env.GOOGLE_DRIVE_FOLDER_ID;
|
const GOOGLE_DRIVE_FOLDER_ID = process.env.GOOGLE_DRIVE_FOLDER_ID;
|
||||||
|
|
||||||
const SCOPES = ["https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/drive.file"]
|
const SCOPES = ["https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/drive.file"]
|
||||||
const MAX_RETRY = 3;
|
|
||||||
|
|
||||||
export const LogRowDataSchema = z.object({
|
export const LogRowDataSchema = z.object({
|
||||||
timestamp: z.string(),
|
timestamp: z.string(),
|
||||||
|
|
@ -49,10 +49,27 @@ export const googleDriveController = {
|
||||||
const docs = google.docs({ version: "v1", auth: auth });
|
const docs = google.docs({ version: "v1", auth: auth });
|
||||||
return docs;
|
return docs;
|
||||||
},
|
},
|
||||||
|
checkConnection: async (driveClient: drive_v3.Drive): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
const res = await driveClient.files.list({
|
||||||
|
corpora: 'drive',
|
||||||
|
driveId: GOOGLE_DRIVE_FOLDER_ID,
|
||||||
|
pageSize: 1,
|
||||||
|
fields: "files(id, name)",
|
||||||
|
includeItemsFromAllDrives: true,
|
||||||
|
includeTeamDriveItems: true,
|
||||||
|
supportsAllDrives: true
|
||||||
|
});
|
||||||
|
console.log("Google Drive connection check successful:", res.data);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error checking Google Drive connection:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
uploadFile: async (driveClient: drive_v3.Drive, filePath: string, folderId: string, fileName: string, contentType: string): Promise<string | null> => {
|
uploadFile: async (driveClient: drive_v3.Drive, filePath: string, folderId: string, fileName: string, contentType: string): Promise<string | null> => {
|
||||||
try {
|
try {
|
||||||
// console.log("Uploading file to Google Drive:", filePath);
|
|
||||||
const response = await driveClient.files.create({
|
const response = await driveClient.files.create({
|
||||||
requestBody: {
|
requestBody: {
|
||||||
name: fileName,
|
name: fileName,
|
||||||
|
|
@ -62,6 +79,7 @@ export const googleDriveController = {
|
||||||
mimeType: contentType,
|
mimeType: contentType,
|
||||||
body: fs.createReadStream(filePath),
|
body: fs.createReadStream(filePath),
|
||||||
},
|
},
|
||||||
|
supportsAllDrives: true,
|
||||||
});
|
});
|
||||||
if(!response.data.id) return null;
|
if(!response.data.id) return null;
|
||||||
return response.data.id;
|
return response.data.id;
|
||||||
|
|
@ -123,6 +141,7 @@ export const googleDriveController = {
|
||||||
|
|
||||||
const file = await driveClient.files.create({
|
const file = await driveClient.files.create({
|
||||||
requestBody,
|
requestBody,
|
||||||
|
supportsAllDrives: true,
|
||||||
// fields: 'id',
|
// fields: 'id',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -134,6 +153,35 @@ export const googleDriveController = {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
createMinutesDocument: async(driveClient: drive_v3.Drive, folderId: string, fileName: string, htmlText: string): Promise<string | null> => {
|
||||||
|
try {
|
||||||
|
const requestBody = {
|
||||||
|
name: fileName,
|
||||||
|
parents: [folderId], // 作成したフォルダのIDを指定
|
||||||
|
mimeType: DOCUMENT_MIMETYPE,
|
||||||
|
};
|
||||||
|
|
||||||
|
const media = {
|
||||||
|
mimeType: 'text/html',
|
||||||
|
body: Readable.from([htmlText])
|
||||||
|
};
|
||||||
|
|
||||||
|
const file = await driveClient.files.create({
|
||||||
|
requestBody,
|
||||||
|
media,
|
||||||
|
supportsAllDrives: true,
|
||||||
|
// fields: 'id',
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('File Id:', file.data);
|
||||||
|
if (!file.data.id) return null;
|
||||||
|
return file.data.id;
|
||||||
|
} catch(err) {
|
||||||
|
console.error('Error creating file:', err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
// CAUTION
|
// CAUTION
|
||||||
deleteFile: async (driveClient: drive_v3.Drive, fileId: string) => {
|
deleteFile: async (driveClient: drive_v3.Drive, fileId: string) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -147,31 +195,31 @@ export const googleDriveController = {
|
||||||
console.error('Error deleting file:', error);
|
console.error('Error deleting file:', error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addContentToDocs: async (docsClient: docs_v1.Docs, documentId: string, content: string): Promise<boolean> => {
|
// addContentToDocs: async (docsClient: docs_v1.Docs, documentId: string, content: string): Promise<boolean> => {
|
||||||
try {
|
// try {
|
||||||
const requestBody: docs_v1.Schema$BatchUpdateDocumentRequest = {
|
// const requestBody: docs_v1.Schema$BatchUpdateDocumentRequest = {
|
||||||
requests: [
|
// requests: [
|
||||||
{
|
// {
|
||||||
insertText: {
|
// insertText: {
|
||||||
text: content,
|
// text: content,
|
||||||
location: {
|
// location: {
|
||||||
index: 1,
|
// index: 1,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
]
|
// ]
|
||||||
};
|
// };
|
||||||
const response = await docsClient.documents.batchUpdate({
|
// const response = await docsClient.documents.batchUpdate({
|
||||||
documentId: documentId,
|
// documentId: documentId,
|
||||||
requestBody: requestBody,
|
// requestBody: requestBody,
|
||||||
});
|
// });
|
||||||
console.log('Content added to document:', response.data);
|
// console.log('Content added to document:', response.data);
|
||||||
return true;
|
// return true;
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error('Error adding content to document:', error);
|
// console.error('Error adding content to document:', error);
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
|
|
||||||
getLogSheetId: async (driveClient: drive_v3.Drive, sheetsClient: sheets_v4.Sheets, folderId: string, fileName: string): Promise<string | null> => {
|
getLogSheetId: async (driveClient: drive_v3.Drive, sheetsClient: sheets_v4.Sheets, folderId: string, fileName: string): Promise<string | null> => {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ const hubspotClient = new Client({ accessToken: process.env.HUBSPOT_ACCESS_TOKEN
|
||||||
export const CompanySchema = z.object({
|
export const CompanySchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
})
|
});
|
||||||
|
|
||||||
export const OwnerSchema = z.object({
|
export const OwnerSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
|
|
@ -19,9 +19,16 @@ export type Company = z.infer<typeof CompanySchema>;
|
||||||
export type Owner = z.infer<typeof OwnerSchema>;
|
export type Owner = z.infer<typeof OwnerSchema>;
|
||||||
|
|
||||||
export const hubspotController = {
|
export const hubspotController = {
|
||||||
check: async() => {
|
check: async(): Promise<boolean | null> => {
|
||||||
const response = await hubspotClient.crm.companies.getAll();
|
try {
|
||||||
console.log(response.length);
|
const response = await hubspotClient.crm.companies.getAll();
|
||||||
|
console.log(response.length);
|
||||||
|
console.log("HubSpot connection check successful.");
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("HubSpot connection check failed:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getCompanies: async(): Promise<Company[] | null> => {
|
getCompanies: async(): Promise<Company[] | null> => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -33,9 +40,9 @@ export const hubspotController = {
|
||||||
const response = await hubspotClient.crm.companies.basicApi.getPage(limit, after);
|
const response = await hubspotClient.crm.companies.basicApi.getPage(limit, after);
|
||||||
// console.log(response.results);
|
// console.log(response.results);
|
||||||
const companies: Company[] = response.results.map((company) => CompanySchema.parse({
|
const companies: Company[] = response.results.map((company) => CompanySchema.parse({
|
||||||
id: company.id,
|
id: company.id,
|
||||||
name: company.properties.name,
|
name: company.properties.name ?? '',
|
||||||
}));
|
}));
|
||||||
allCompanies.push(...companies);
|
allCompanies.push(...companies);
|
||||||
|
|
||||||
if(response.paging && response.paging.next && response.paging.next.after) {
|
if(response.paging && response.paging.next && response.paging.next.after) {
|
||||||
|
|
@ -46,6 +53,7 @@ export const hubspotController = {
|
||||||
}
|
}
|
||||||
return allCompanies;
|
return allCompanies;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("Error fetching companies:", error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { storageController } from "./storage";
|
||||||
import { CLOUD_STORAGE_MASTER_FOLDER_NAME, DATE_FORMAT, DATETIME_FORMAT, DOCUMENT_MIMETYPE, OWNERS_FILE_NAME, YM_FORMAT } from "../../serverConfig";
|
import { CLOUD_STORAGE_MASTER_FOLDER_NAME, DATE_FORMAT, DATETIME_FORMAT, DOCUMENT_MIMETYPE, OWNERS_FILE_NAME, YM_FORMAT } from "../../serverConfig";
|
||||||
import { hubspotController, OwnerSchema } from "./hubspot";
|
import { hubspotController, OwnerSchema } from "./hubspot";
|
||||||
import { fuzzyMatchController } from "./fuzzyMatch";
|
import { fuzzyMatchController } from "./fuzzyMatch";
|
||||||
|
import { marked } from "marked";
|
||||||
|
|
||||||
const VideoInfoSchema = z.looseObject({
|
const VideoInfoSchema = z.looseObject({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
|
|
@ -63,8 +64,9 @@ export const processRequest = async (videoInfo: VideoInfo) => {
|
||||||
const sheetsClient = googleDriveController.getSheetsClient(googleAuth);
|
const sheetsClient = googleDriveController.getSheetsClient(googleAuth);
|
||||||
|
|
||||||
const jstStartsAt = dateController.convertToJst(startsAt);
|
const jstStartsAt = dateController.convertToJst(startsAt);
|
||||||
|
const meetingDateStr = dateController.getFormattedDate(jstStartsAt, "yyyy年MM月dd日");
|
||||||
const jstEndsAt = dateController.convertToJst(endsAt);
|
const jstEndsAt = dateController.convertToJst(endsAt);
|
||||||
const fileName = fileController.createMinutesFileName(title, hostName, jstStartsAt);
|
const fileName = fileController.createMinutesFileName(title, hostName, meetingDateStr);
|
||||||
const videoUrl = `${MIITEL_URL}app/video/${videoId}`;
|
const videoUrl = `${MIITEL_URL}app/video/${videoId}`;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -74,23 +76,21 @@ export const processRequest = async (videoInfo: VideoInfo) => {
|
||||||
const createZip = await fileController.createZip(videoInfo, outputPath, fileName);
|
const createZip = await fileController.createZip(videoInfo, outputPath, fileName);
|
||||||
if(!createZip) throw createCustomError("CREATE_ZIP_FILE_FAILED");
|
if(!createZip) throw createCustomError("CREATE_ZIP_FILE_FAILED");
|
||||||
|
|
||||||
const logFileId = await callFunctionWithRetry(() => googleDriveController.uploadFile(driveClient, outputPath, MIITEL_REQUEST_LOG_FOLDER_ID, `${fileName}.zip`, "application/zip"));
|
const logFileId = await callFunctionWithRetry(() => googleDriveController.uploadFile(driveClient, outputPath, MIITEL_REQUEST_LOG_FOLDER_ID, fileName + '.zip', "application/zip"));
|
||||||
if(!logFileId) throw createCustomError("UPLOAD_LOG_FAILED");
|
if(!logFileId) throw createCustomError("UPLOAD_LOG_FAILED");
|
||||||
|
|
||||||
// ===== Generate Minutes =====
|
// ===== Generate Minutes =====
|
||||||
const minutes = await callFunctionWithRetry(() => aiController.generateMinutes(speechRecognition));
|
const minutes = await callFunctionWithRetry(() => aiController.generateMinutes(speechRecognition));
|
||||||
|
console.log(minutes);
|
||||||
if (!minutes) throw createCustomError("AI_GENERATION_FAILED");
|
if (!minutes) throw createCustomError("AI_GENERATION_FAILED");
|
||||||
let content = `会議履歴URL:${videoUrl}\n`;
|
const html = await marked.parse(minutes);
|
||||||
content += `担当者:${hostName}\n\n`;
|
let content = `<p>会議履歴URL:<a href="${videoUrl}">${videoUrl}</a></p>`;
|
||||||
content += minutes;
|
content += `<p>担当者:${hostName}</p>`;
|
||||||
|
content += html;
|
||||||
|
|
||||||
// ===== Upload To Google Drive =====
|
// ===== Upload To Google Drive =====
|
||||||
const documentId = await callFunctionWithRetry(() => googleDriveController.createNewFile(driveClient, GOOGLE_DRIVE_FOLDER_ID, fileName, DOCUMENT_MIMETYPE));
|
const documentId = await callFunctionWithRetry(() => googleDriveController.createMinutesDocument(driveClient, GOOGLE_DRIVE_FOLDER_ID, fileName, content));
|
||||||
if (!documentId) throw createCustomError("CREATE_NEW_DOCUMENT_FAILED");
|
if (!documentId) throw createCustomError("CREATE_NEW_DOCUMENT_FAILED");
|
||||||
const addContentResult = await callFunctionWithRetry(() => googleDriveController.addContentToDocs(docsClient, documentId, content));
|
|
||||||
if(!addContentResult) throw createCustomError("UPLOAD_MINUTES_FAILED");
|
|
||||||
|
|
||||||
|
|
||||||
// ===== Create Meeting Log at Hubspot =====
|
// ===== Create Meeting Log at Hubspot =====
|
||||||
const ownersJson = await callFunctionWithRetry(() => storageController.loadJsonFromGCS(CLOUD_STORAGE_MASTER_FOLDER_NAME, OWNERS_FILE_NAME));
|
const ownersJson = await callFunctionWithRetry(() => storageController.loadJsonFromGCS(CLOUD_STORAGE_MASTER_FOLDER_NAME, OWNERS_FILE_NAME));
|
||||||
|
|
@ -102,7 +102,7 @@ export const processRequest = async (videoInfo: VideoInfo) => {
|
||||||
const extractedCompanyName = fileController.extractCompanyNameFromTitle(title);
|
const extractedCompanyName = fileController.extractCompanyNameFromTitle(title);
|
||||||
const matchedCompany = await fuzzyMatchController.searchMatchedCompany(extractedCompanyName);
|
const matchedCompany = await fuzzyMatchController.searchMatchedCompany(extractedCompanyName);
|
||||||
if(matchedCompany) {
|
if(matchedCompany) {
|
||||||
const createLogResult = await callFunctionWithRetry(() => hubspotController.createMeetingLog(matchedCompany.id, title, ownerId, minutes, startsAt, endsAt));
|
const createLogResult = await callFunctionWithRetry(() => hubspotController.createMeetingLog(matchedCompany.id, title, ownerId, content, startsAt, endsAt));
|
||||||
if(!createLogResult) throw createCustomError("CREATE_MEETING_LOG_FAILED");
|
if(!createLogResult) throw createCustomError("CREATE_MEETING_LOG_FAILED");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,10 +113,9 @@ export const processRequest = async (videoInfo: VideoInfo) => {
|
||||||
if(!sheetId) throw createCustomError("GET_SHEET_ID_FAILED");
|
if(!sheetId) throw createCustomError("GET_SHEET_ID_FAILED");
|
||||||
|
|
||||||
const currentJstDateTimeStr = dateController.getCurrentJstTime(DATETIME_FORMAT);
|
const currentJstDateTimeStr = dateController.getCurrentJstTime(DATETIME_FORMAT);
|
||||||
const currentJstDateStr = dateController.getCurrentJstTime(DATE_FORMAT);
|
|
||||||
const rowData: LogRowData = LogRowDataSchema.parse({
|
const rowData: LogRowData = LogRowDataSchema.parse({
|
||||||
timestamp: currentJstDateTimeStr,
|
timestamp: currentJstDateTimeStr,
|
||||||
meetingDate: currentJstDateStr,
|
meetingDate: meetingDateStr,
|
||||||
title: title,
|
title: title,
|
||||||
matchedCompanyName: matchedCompany?.name ?? '',
|
matchedCompanyName: matchedCompany?.name ?? '',
|
||||||
ownerName: hostName,
|
ownerName: hostName,
|
||||||
|
|
@ -133,7 +132,7 @@ export const processRequest = async (videoInfo: VideoInfo) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const logUploadProcess = async (videoInfo: VideoInfo) => {
|
export const logUploadProcess = async (videoInfo: VideoInfo): Promise<boolean | null> => {
|
||||||
try {
|
try {
|
||||||
const videoId = videoInfo.id;
|
const videoId = videoInfo.id;
|
||||||
const title = videoInfo.title;
|
const title = videoInfo.title;
|
||||||
|
|
@ -144,7 +143,7 @@ export const logUploadProcess = async (videoInfo: VideoInfo) => {
|
||||||
const hostName = videoInfo.host.user_name;
|
const hostName = videoInfo.host.user_name;
|
||||||
const speechRecognition = videoInfo.speech_recognition.raw;
|
const speechRecognition = videoInfo.speech_recognition.raw;
|
||||||
|
|
||||||
if (accessPermission !== "EVERYONE" || !title.includes("様") || title.includes("社内")) return;
|
if (accessPermission !== "EVERYONE" || !title.includes("様") || title.includes("社内")) return true;
|
||||||
|
|
||||||
// ===== Init =====
|
// ===== Init =====
|
||||||
const googleAuth = await googleDriveController.getAuth();
|
const googleAuth = await googleDriveController.getAuth();
|
||||||
|
|
@ -153,8 +152,9 @@ export const logUploadProcess = async (videoInfo: VideoInfo) => {
|
||||||
const sheetsClient = googleDriveController.getSheetsClient(googleAuth);
|
const sheetsClient = googleDriveController.getSheetsClient(googleAuth);
|
||||||
|
|
||||||
const jstStartsAt = dateController.convertToJst(startsAt);
|
const jstStartsAt = dateController.convertToJst(startsAt);
|
||||||
|
const meetingDateStr = dateController.getFormattedDate(jstStartsAt, "yyyy年MM月dd日");
|
||||||
const jstEndsAt = dateController.convertToJst(endsAt);
|
const jstEndsAt = dateController.convertToJst(endsAt);
|
||||||
const fileName = fileController.createMinutesFileName(title, hostName, jstStartsAt);
|
const fileName = fileController.createMinutesFileName(title, hostName, meetingDateStr);
|
||||||
const videoUrl = `${MIITEL_URL}app/video/${videoId}`;
|
const videoUrl = `${MIITEL_URL}app/video/${videoId}`;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -166,6 +166,27 @@ export const logUploadProcess = async (videoInfo: VideoInfo) => {
|
||||||
|
|
||||||
const logFileId = await callFunctionWithRetry(() => googleDriveController.uploadFile(driveClient, outputPath, MIITEL_REQUEST_LOG_FOLDER_ID, `${fileName}.zip`, "application/zip"));
|
const logFileId = await callFunctionWithRetry(() => googleDriveController.uploadFile(driveClient, outputPath, MIITEL_REQUEST_LOG_FOLDER_ID, `${fileName}.zip`, "application/zip"));
|
||||||
if(!logFileId) throw createCustomError("UPLOAD_LOG_FAILED");
|
if(!logFileId) throw createCustomError("UPLOAD_LOG_FAILED");
|
||||||
|
fs.unlinkSync(outputPath);
|
||||||
|
return true;
|
||||||
|
} catch(error) {
|
||||||
|
console.log(error);
|
||||||
|
fs.unlinkSync(outputPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const testProcess = async () => {
|
||||||
|
try {
|
||||||
|
// Google Drive 接続確認
|
||||||
|
const googleAuth = await googleDriveController.getAuth();
|
||||||
|
const driveClilent = googleDriveController.getDriveClient(googleAuth);
|
||||||
|
const driveResponse = await googleDriveController.checkConnection(driveClilent);
|
||||||
|
if(!driveResponse) throw createCustomError("CONNECT_GOOGLE_DRIVE_FAILED");
|
||||||
|
|
||||||
|
// Hubspot 接続確認
|
||||||
|
const hubspotResponse = await hubspotController.check();
|
||||||
|
if(!hubspotResponse) throw createCustomError("CONNECT_HUBSPOT_FAILED");
|
||||||
|
return;
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export const storageController = {
|
||||||
},
|
},
|
||||||
loadFromGCS: async(folder: string, filename: string): Promise<string | null> => {
|
loadFromGCS: async(folder: string, filename: string): Promise<string | null> => {
|
||||||
const file = bucket.file(`${folder}/${filename}`);
|
const file = bucket.file(`${folder}/${filename}`);
|
||||||
// console.log("loading file:", file.name);
|
console.log("loading file:", `${folder}/${filename}`);
|
||||||
try {
|
try {
|
||||||
const [data] = await file.download();
|
const [data] = await file.download();
|
||||||
return zlib.gunzipSync(data).toString("utf-8");
|
return zlib.gunzipSync(data).toString("utf-8");
|
||||||
|
|
@ -46,15 +46,20 @@ export const storageController = {
|
||||||
},
|
},
|
||||||
getFileList: async(): Promise<string[] | null> => {
|
getFileList: async(): Promise<string[] | null> => {
|
||||||
try {
|
try {
|
||||||
const files = await bucket.getFiles({
|
const results = await bucket.getFiles({
|
||||||
prefix: 'request_log/',
|
prefix: 'request_log/',
|
||||||
});
|
});
|
||||||
const list = [];
|
const files = results[0];
|
||||||
for(const f of files[0]) {
|
files.sort((a, b) => {
|
||||||
// console.log(f.name)
|
if(!a.metadata.timeCreated || !b.metadata.timeCreated) return 0;
|
||||||
list.push(f.name);
|
const timeA = new Date(a.metadata.timeCreated).getTime();
|
||||||
}
|
const timeB = new Date(b.metadata.timeCreated).getTime();
|
||||||
return list;
|
return timeA - timeB;
|
||||||
|
});
|
||||||
|
// for(const f of files[0]) {
|
||||||
|
// list.push(f.name);
|
||||||
|
// }
|
||||||
|
return files.map((f) => f.name);
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,36 @@
|
||||||
|
|
||||||
export const ERROR_DEFINITIONS = {
|
export const ERROR_DEFINITIONS = {
|
||||||
ZOD_FAILED: { code: "E1003", message: "zodのチェックが失敗しました", statusCode: -1 },
|
ZOD_FAILED: { code: "E1003", message: "zodのチェックが失敗しました", statusCode: -1 },
|
||||||
// ログ ZIP の Google Drive アップロード失敗
|
|
||||||
UPLOAD_LOG_FAILED: { code: "E3001", message: "ログファイルのアップロードに失敗しました", statusCode: 500 },
|
// Google Drive関連
|
||||||
|
|
||||||
// AI による議事録生成失敗
|
|
||||||
AI_GENERATION_FAILED: { code: "E2001", message: "AIによる議事録生成に失敗しました", statusCode: 500 },
|
|
||||||
|
|
||||||
// 議事録(Google Docs)の作成/アップロード失敗
|
// 議事録(Google Docs)の作成/アップロード失敗
|
||||||
CREATE_NEW_DOCUMENT_FAILED: { code: "E3002", message: "ドキュメント作成に失敗しました", statusCode: 500 },
|
CONNECT_GOOGLE_DRIVE_FAILED: { code: "E2001", message: "ファイル一覧取得に失敗しました", statusCode: 500 },
|
||||||
UPLOAD_MINUTES_FAILED: { code: "E3003", message: "議事録のアップロードに失敗しました", statusCode: 500 },
|
GET_FOLDER_ID_FAILED: { code: "E2002", message: "フォルダID取得に失敗しました", statusCode: 500 },
|
||||||
|
GET_SHEET_ID_FAILED: { code: "E2003", message: "スプレッドシートID取得に失敗しました", statusCode: 500 },
|
||||||
|
|
||||||
|
CREATE_NEW_DOCUMENT_FAILED: { code: "E2004", message: "ドキュメント作成に失敗しました", statusCode: 500 },
|
||||||
|
|
||||||
|
UPLOAD_MINUTES_FAILED: { code: "E2005", message: "議事録のアップロードに失敗しました", statusCode: 500 },
|
||||||
|
UPLOAD_LOG_FAILED: { code: "E2006", message: "ログファイルのアップロードに失敗しました", statusCode: 500 },
|
||||||
|
|
||||||
|
INSERT_ROW_FAILED: { code: "E2007", message: "シートへのデータ追加に失敗しました", statusCode: 500 },
|
||||||
|
|
||||||
|
// Hubspot関連
|
||||||
// オーナー情報の取得失敗
|
// オーナー情報の取得失敗
|
||||||
|
CONNECT_HUBSPOT_FAILED: { code: "E3001", message: "ファイル一覧取得に失敗しました", statusCode: 500 },
|
||||||
GET_OWNERS_FAILED: { code: "E3004", message: "オーナー情報の取得に失敗しました", statusCode: 500 },
|
GET_OWNERS_FAILED: { code: "E3004", message: "オーナー情報の取得に失敗しました", statusCode: 500 },
|
||||||
GET_COMPANIES_FAILED: { code: "E3005", message: "会社情報の取得に失敗しました", statusCode: 500 },
|
GET_COMPANIES_FAILED: { code: "E3005", message: "会社情報の取得に失敗しました", statusCode: 500 },
|
||||||
|
|
||||||
GET_FOLDER_ID_FAILED: { code: "E3007", message: "フォルダID取得に失敗しました", statusCode: 500 },
|
|
||||||
GET_SHEET_ID_FAILED: { code: "E3008", message: "スプレッドシートID取得に失敗しました", statusCode: 500 },
|
|
||||||
CREATE_ZIP_FILE_FAILED: { code: "E3009", message: "ZIPファイルの作成に失敗しました", statusCode: 500 },
|
|
||||||
INSERT_ROW_FAILED: { code: "E3009", message: "シートへのデータ追加に失敗しました", statusCode: 500 },
|
|
||||||
|
|
||||||
GET_FILES_FAILED: { code: "E3010", message: "ファイルの取得に失敗しました", statusCode: 500 },
|
GET_FILES_FAILED: { code: "E3010", message: "ファイルの取得に失敗しました", statusCode: 500 },
|
||||||
CREATE_MEETING_LOG_FAILED: { code: "E3011", message: "ミーティングログ作成に失敗しました", statusCode: 500 },
|
CREATE_MEETING_LOG_FAILED: { code: "E3011", message: "ミーティングログ作成に失敗しました", statusCode: 500 },
|
||||||
|
|
||||||
|
// AI による議事録生成失敗
|
||||||
|
AI_GENERATION_FAILED: { code: "E4001", message: "AIによる議事録生成に失敗しました", statusCode: 500 },
|
||||||
|
|
||||||
|
|
||||||
|
CREATE_ZIP_FILE_FAILED: { code: "E3007", message: "ZIPファイルの作成に失敗しました", statusCode: 500 },
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type ErrorKey = keyof typeof ERROR_DEFINITIONS;
|
export type ErrorKey = keyof typeof ERROR_DEFINITIONS;
|
||||||
|
|
|
||||||
|
|
@ -30,22 +30,6 @@ resource "google_project_iam_member" "cf_sa_role" {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Cloud Workflows用サービスアカウント
|
|
||||||
resource "google_service_account" "workflows_sa" {
|
|
||||||
project = var.project_id
|
|
||||||
account_id = "mrt-cloudworkflows-sa"
|
|
||||||
display_name = "Cloud Workflows SA"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 権限を SA に付与
|
|
||||||
resource "google_project_iam_member" "wf_cf_role" {
|
|
||||||
for_each = toset(["roles/cloudfunctions.invoker","roles/run.invoker"])
|
|
||||||
project = var.project_id
|
|
||||||
role = each.value
|
|
||||||
member = "serviceAccount:${google_service_account.workflows_sa.email}"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# API Gateway用サービスアカウント
|
# API Gateway用サービスアカウント
|
||||||
resource "google_service_account" "gateway_sa" {
|
resource "google_service_account" "gateway_sa" {
|
||||||
project = var.project_id
|
project = var.project_id
|
||||||
|
|
@ -62,17 +46,17 @@ resource "google_project_iam_member" "gateway_role" {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# cloud build用サービスアカウント
|
# Scheduler実行用サービスアカウント
|
||||||
resource "google_service_account" "cloudbuild_sa" {
|
resource "google_service_account" "cf_scheduler_sa" {
|
||||||
project = var.project_id
|
project = var.project_id
|
||||||
account_id = "mrt-cloudbuild-sa"
|
account_id = "mrt-scheduler-sa"
|
||||||
display_name = "Cloud Build 用サービスアカウント"
|
display_name = "Cloud Functions 起動用サービスアカウント"
|
||||||
}
|
}
|
||||||
|
|
||||||
# 権限を SA に付与
|
# 権限を SA に付与
|
||||||
resource "google_project_iam_member" "cloudbuild_role" {
|
resource "google_project_iam_member" "scheduler_role" {
|
||||||
for_each = toset(["roles/cloudbuild.builds.builder","roles/storage.objectAdmin", "roles/artifactregistry.writer", "roles/developerconnect.readTokenAccessor", "roles/cloudfunctions.developer","roles/workflows.admin", "roles/iam.serviceAccountUser"])
|
for_each = toset(["roles/cloudfunctions.invoker","roles/run.invoker"])
|
||||||
project = var.project_id
|
project = var.project_id
|
||||||
role = each.value
|
role = each.value
|
||||||
member = "serviceAccount:${google_service_account.cloudbuild_sa.email}"
|
member = "serviceAccount:${google_service_account.cf_scheduler_sa.email}"
|
||||||
}
|
}
|
||||||
|
|
@ -10,37 +10,22 @@ variable "region" {
|
||||||
|
|
||||||
variable "function_name" {
|
variable "function_name" {
|
||||||
type = string
|
type = string
|
||||||
default = "mrt-create-log-sheet"
|
default = "generate-minutes"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Scheduler実行用サービスアカウント
|
|
||||||
resource "google_service_account" "cf_scheduler_sa" {
|
|
||||||
project = var.project_id
|
|
||||||
account_id = "mrt-scheduler-sa"
|
|
||||||
display_name = "Cloud Functions 起動用サービスアカウント"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 権限を SA に付与
|
# 毎日3時に Function を実行する Scheduler ジョブ
|
||||||
resource "google_project_iam_member" "scheduler_role" {
|
resource "google_cloud_scheduler_job" "daily_cf_trigger" {
|
||||||
for_each = toset(["roles/cloudfunctions.invoker","roles/run.invoker"])
|
|
||||||
project = var.project_id
|
|
||||||
role = each.value
|
|
||||||
member = "serviceAccount:${google_service_account.cf_scheduler_sa.email}"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# 毎月1日0時に Function を実行する Scheduler ジョブ
|
|
||||||
resource "google_cloud_scheduler_job" "monthly_cf_trigger" {
|
|
||||||
project = var.project_id
|
project = var.project_id
|
||||||
name = "monthly-cf-trigger"
|
name = "daily-cf-trigger"
|
||||||
description = "Invoke Cloud Function on the 1st of each month at 00:00"
|
description = "Invoke Cloud Function everyday at 03:00"
|
||||||
region = var.region
|
region = var.region
|
||||||
schedule = "0 0 1 * *"
|
schedule = "0 3 * * *"
|
||||||
time_zone = "Asia/Tokyo"
|
time_zone = "Asia/Tokyo"
|
||||||
|
|
||||||
http_target {
|
http_target {
|
||||||
uri = "https://${var.region}-${var.project_id}.cloudfunctions.net/${var.function_name}"
|
uri = "https://${var.region}-${var.project_id}.cloudfunctions.net/${var.function_name}/api/dailyBatch"
|
||||||
http_method = "POST"
|
http_method = "POST"
|
||||||
oidc_token {
|
oidc_token {
|
||||||
service_account_email = google_service_account.cf_scheduler_sa.email
|
service_account_email = google_service_account.cf_scheduler_sa.email
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue