diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 8459169..647ca2f 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -1,8 +1,7 @@ name: Build docs on: - push: - branches: - - main + release: + types: [ published ] jobs: build: diff --git a/docker-compose.yml b/docker-compose.yml index 7f76093..5956757 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,11 +2,21 @@ version: '3.8' services: app: - build: . + image: sammrai/mfapi:latest + container_name: mfapi + ports: + - 3001:3001 volumes: - type: bind source: ./data target: /data - type: bind source: ./config.json - target: /app/config.json \ No newline at end of file + target: /app/config.json + # 開発用 + # - type: bind + # source: ./src + # target: /app/src + # build: . + # command: /bin/sh + # tty: true diff --git a/entrypoint.sh b/entrypoint.sh index 6fe7c1c..7344302 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,3 +1,9 @@ #!/bin/sh -yarn start || true \ No newline at end of file +while : +do + yarn start || true + + echo "Waiting 10s before restarting..." + sleep 10 +done \ No newline at end of file diff --git a/package.json b/package.json index f2f48ee..1ca77ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mfapi", - "version": "0.0.1", + "version": "0.0.2", "description": "", "main": "dist/main.js", "repository": "git@github.com:sammrai/mfapi.git", diff --git a/src/actions/api.ts b/src/actions/api.ts index 39b6ba4..c4d6a95 100644 --- a/src/actions/api.ts +++ b/src/actions/api.ts @@ -44,7 +44,7 @@ export abstract class ApiResponseHandler { try { const response = await this.axiosInstance.post(url, data); // console.log("SUCCESS:", response.data); - console.log("Status Code: ", response.status); + console.log("post", url, response.status); // console.log("Response Headers: ", response.headers); return response.data; } catch (error) { @@ -58,7 +58,7 @@ export abstract class ApiResponseHandler { decorator: (cheerio: CheerioAPI) => T ): Promise { const response = await this.axiosInstance.get(url); - console.log("Status Code: ", response.status); + console.log("get", url, response.status); // console.log("Response Headers: ", response.headers); const $ = cheerio.load(response.data); return decorator($); diff --git a/src/actions/asset.ts b/src/actions/asset.ts index ae083bb..16b3d39 100644 --- a/src/actions/asset.ts +++ b/src/actions/asset.ts @@ -141,7 +141,7 @@ export class Asset extends ApiResponseHandler { $('form.form-horizontal[action="/bs/portfolio/edit"]').each((_, form) => { const formId = $(form).attr("id")?.replace("new_user_asset_det_", ""); - const formInputs = $(form).find(".control-group input[id]"); + const formInputs = $(form).find("input[id]"); const detail: Partial = {}; if (formId) { @@ -156,6 +156,9 @@ export class Asset extends ApiResponseHandler { switch (id.replace("user_asset_det_", "")) { case "sub_account_id_hash": break; + case "id": + detail.assetId += `@${value}`; + break; case "temp_asset_subclass_id": break; case "asset_subclass_id": @@ -190,7 +193,7 @@ export class Asset extends ApiResponseHandler { accountString: string, assetId: string ): Promise { - const url = `/bs/portfolio/${assetId}?sub_account_id_hash=${ + const url = `/bs/portfolio/${assetId.split("@")[0]}?sub_account_id_hash=${ accountString.split("@")[1] }`; const postData = { @@ -198,4 +201,29 @@ export class Asset extends ApiResponseHandler { }; return this.post(url, postData); } + + public async updateAsset( + accountString: string, + assetId: string, + assetSubclassId: AssetSubclass, + assetName: string, + assetValue: number, + assetEntryValue?: number, + assetEntryAt?: Date + ): Promise { + const postData = { + _method: "put", + "user_asset_det[id]": assetId.split("@")[1], + "user_asset_det[sub_account_id_hash]": accountString.split("@")[1], + "user_asset_det[asset_subclass_id]": assetSubclassId, + "user_asset_det[name]": assetName, + "user_asset_det[value]": assetValue, + "user_asset_det[entried_price]": + assetEntryValue !== undefined ? assetEntryValue : "", + "user_asset_det[entried_at]": assetEntryAt + ? this.formatDate(assetEntryAt) + : "", + }; + return this.post("/bs/portfolio/edit", postData); + } } diff --git a/src/main.ts b/src/main.ts index b542bf4..67dcb4a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -75,11 +75,15 @@ async function main() { app.get( "/accounts/:accountString/assets", async (req: Request, res: Response) => { - const { accountString } = req.params; - const portfolios: AssetModel[] = await assetController.getAssets( - accountString - ); - res.status(200).json(portfolios); + try { + const { accountString } = req.params; + const portfolios: AssetModel[] = await assetController.getAssets( + accountString + ); + res.status(200).json(portfolios); + } catch (error) { + res.status(500).send(); + } } ); @@ -110,9 +114,36 @@ async function main() { try { const { accountString, assetId } = req.params; await assetController.deleteAsset(accountString, assetId); - res.status(200).send(); + res.status(204).send(); + } catch (error) { + const err = error as CustomError; + if (err.status === 500) { + res.status(404).send(); + } else { + res.status(500).send(); + } + } + } + ); + + app.put( + "/accounts/:accountString/assets/:assetId", + async (req: Request, res: Response) => { + try { + const { accountString, assetId } = req.params; + const { assetSubclassId, name, value } = req.body; + const _assetSubclassId = + AssetSubclass[assetSubclassId as keyof typeof AssetSubclass]; + await assetController.updateAsset( + accountString, + assetId, + _assetSubclassId, + name, + value + ); + res.status(204).send(); } catch (error) { - res.status(404).send(); + res.status(500).send(); } } ); diff --git a/src/openapi.yaml b/src/openapi.yaml index 9f54b24..dedd20c 100644 --- a/src/openapi.yaml +++ b/src/openapi.yaml @@ -1,9 +1,12 @@ openapi: 3.0.0 info: title: マネーフォワード操作API - version: 0.0.1 + version: 0.0.2 description: | - マネーフォワードME を操作するAPIラッパーを提供。参照系は問題なく動作しますが更新系は更新結果の応答をサイトから得られないため実際に更新できているか確認が必要です。 + マネーフォワードMEのデータを操作するためのREST APIです。このAPIは、マネーフォワードMEの資産管理機能を拡張し、プログラムによる自動操作を可能にします。 + + # ensureオプション(開発中) + 現状では、更新系API(追加、更新、削除など)では、2xx応答が返ってきても、それはサイトにリクエストを投げたことを示すだけで、実際に作成、更新、削除が行われたことの保証はされません。そのため、操作後にリソースの状態を確認し、変更が正確に反映されているかを確かめることが重要です。この確認プロセスを効率化するために、`ensure` オプションが推奨されます。`ensure` オプションを有効化すると、操作後にリソースの状態を自動的に確認する追加のリクエストが発行されますが、これにより全体のリクエスト量が増加し、応答時間が遅くなる可能性があります。また、、サイトへの追加的な負荷も考慮に入れる必要があります。クライアントの要件に応じて、`ensure` オプションの使用要否を検討してください。 contact: url: https://github.com/sammrai/mfapi @@ -11,7 +14,7 @@ info: servers: - url: http://192.168.32.70:3001 description: Local server - - url: http://localhost:3000/api + - url: http://localhost:3001/api description: Local machinie paths: @@ -50,6 +53,8 @@ paths: type: array items: $ref: "#/components/schemas/Asset" + "500": + description: サーバエラー post: tags: [資産] summary: 資産作成 @@ -71,6 +76,10 @@ paths: description: 資産を作成しました "400": description: 入力パラメータが誤っています + "409": + description: 作成に失敗しました + "500": + description: サーバエラー /accounts/{accountString}/assets/{assetId}: delete: @@ -90,10 +99,48 @@ paths: schema: type: string responses: - "200": + "204": description: 資産を削除しました + "409": + description: 削除に失敗しました "404": description: 削除する資産が見つかりません + "500": + description: サーバエラー + put: + tags: [資産] + summary: 資産変更 + parameters: + - name: assetId + description: 資産の一意識別子 + in: path + required: true + schema: + type: string + - name: accountString + description: 口座の接続文字列 + in: path + required: true + schema: + type: string + requestBody: + description: assetSubclassIdは変更することができず、変更前と同じにする必要があります。 + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AssetPost" + responses: + "204": + description: 資産を更新しました + "400": + description: 入力パラメータが誤っています + "404": + description: 更新する資産が見つかりません + "409": + description: 更新に失敗しました + "500": + description: サーバエラー components: schemas: @@ -103,7 +150,7 @@ components: assetId: type: string description: 資産のユニーク識別子 - example: "3GF7x1J_fYqVP90bzOT9CQ3gW-rwRV7PpNpjg1u3CEI" + example: "rwR7x1J_fYqVP90bV7PpN3GFzOT9CQ3gW-pjg1u3CEI" assetSubclassId: $ref: "#/components/schemas/AssetSubclass" name: @@ -123,7 +170,6 @@ components: nullable: true entriedAt: type: string - format: string description: システム登録日 example: "2022/01/01" nullable: true @@ -208,12 +254,16 @@ components: name: type: string description: 口座の名前 + example: カスタムテスト口座名 id: type: string description: 口座の一意識別子 + example: 1oSPZ6UHFklEcqxptwmUi8AsOLXZ-c895CO9eXbmo9j subAccountIdHash: type: string description: 口座制御の一意識別子 + example: Slt9Ua10VeSgHLsvDDxmuHwNrufWSVgUc8Ntzl1yNxL accountString: type: string description: 口座の接続文字列 + example: 1oSPZ6UHFklEcqxptwmUi8AsOLXZ-c895CO9eXbmo9j@Slt9Ua10VeSgHLsvDDxmuHwNrufWSVgUc8Ntzl1yNxL