Compare commits

...

947 Commits

Author SHA1 Message Date
Steven fefe2b0655 chore: fix build artifacts action 2024-07-17 23:16:09 +08:00
Steven 5704aaad08 chore: tweak embeded memo style 2024-07-17 22:48:22 +08:00
Steven ef7e2151dc chore: update memo relation list 2024-07-17 21:40:43 +08:00
Steven e2fd79200e feat: implement inline memo editor 2024-07-17 21:07:36 +08:00
Steven 956f21838d chore: fix popover z-index 2024-07-17 20:28:31 +08:00
Steven 878fea5f13 chore: use popover for update tag view setting 2024-07-17 08:46:52 +08:00
Steven 5a26b76511 chore: add memo content snippet 2024-07-17 08:18:37 +08:00
dependabot[bot] 0053977b14 chore: bump github.com/aws/aws-sdk-go-v2/config from 1.27.24 to 1.27.26 (#3691)
Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.27.24 to 1.27.26.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.27.24...config/v1.27.26)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/config
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-16 22:26:20 +08:00
dependabot[bot] e5c7571eb5 chore: bump github.com/aws/aws-sdk-go-v2/feature/s3/manager from 1.17.4 to 1.17.7 (#3690)
chore: bump github.com/aws/aws-sdk-go-v2/feature/s3/manager

Bumps [github.com/aws/aws-sdk-go-v2/feature/s3/manager](https://github.com/aws/aws-sdk-go-v2) from 1.17.4 to 1.17.7.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/v1.17.7/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/v1.17.4...v1.17.7)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/feature/s3/manager
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-16 22:26:06 +08:00
dependabot[bot] daca7193b5 chore: bump github.com/aws/aws-sdk-go-v2/credentials from 1.17.24 to 1.17.26 (#3693)
chore: bump github.com/aws/aws-sdk-go-v2/credentials

Bumps [github.com/aws/aws-sdk-go-v2/credentials](https://github.com/aws/aws-sdk-go-v2) from 1.17.24 to 1.17.26.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/credentials/v1.17.24...credentials/v1.17.26)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/credentials
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-16 22:24:48 +08:00
dependabot[bot] ca1d3b6eb1 chore: bump modernc.org/sqlite from 1.30.1 to 1.30.2 (#3692)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.30.1 to 1.30.2.
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.30.1...v1.30.2)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-16 22:24:34 +08:00
dependabot[bot] fcdca5b270 chore: bump github.com/aws/aws-sdk-go-v2 from 1.30.1 to 1.30.3 (#3694)
Bumps [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) from 1.30.1 to 1.30.3.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/v1.30.1...v1.30.3)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-16 22:23:46 +08:00
dependabot[bot] 07f79b9f7a chore: bump @mui/joy from 5.0.0-beta.36 to 5.0.0-beta.48 in /web (#3695)
Bumps [@mui/joy](https://github.com/mui/material-ui/tree/HEAD/packages/mui-joy) from 5.0.0-beta.36 to 5.0.0-beta.48.
- [Release notes](https://github.com/mui/material-ui/releases)
- [Changelog](https://github.com/mui/material-ui/blob/next/CHANGELOG.md)
- [Commits](https://github.com/mui/material-ui/commits/HEAD/packages/mui-joy)

---
updated-dependencies:
- dependency-name: "@mui/joy"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-16 22:23:26 +08:00
dependabot[bot] 41d9d3bc21 chore: bump prettier from 3.3.2 to 3.3.3 in /web (#3696)
Bumps [prettier](https://github.com/prettier/prettier) from 3.3.2 to 3.3.3.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.3.2...3.3.3)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-16 22:23:01 +08:00
dependabot[bot] fae951ae2b chore: bump @typescript-eslint/eslint-plugin from 7.15.0 to 7.16.0 in /web (#3697)
chore: bump @typescript-eslint/eslint-plugin in /web

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 7.15.0 to 7.16.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.16.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-16 22:22:44 +08:00
Steven 446e38f15b fix: memo content snippet 2024-07-16 22:19:48 +08:00
dependabot[bot] 7c9f967a07 chore: bump dompurify from 3.1.5 to 3.1.6 in /web (#3698)
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.1.5 to 3.1.6.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.1.5...3.1.6)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-16 09:43:40 +08:00
dependabot[bot] a5d36655e9 chore: bump tailwindcss from 3.4.4 to 3.4.5 in /web (#3699)
Bumps [tailwindcss](https://github.com/tailwindlabs/tailwindcss) from 3.4.4 to 3.4.5.
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/v3.4.5/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/compare/v3.4.4...v3.4.5)

---
updated-dependencies:
- dependency-name: tailwindcss
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-16 09:43:29 +08:00
Steven f2cfc528a6 chore: introduce tag view option 2024-07-15 22:55:59 +08:00
Steven 260a4f89fc chore: buf generate 2024-07-15 22:32:59 +08:00
Johnny 9909fd8a6f feat: add snippet field to memo message (#3689) 2024-07-15 08:08:08 +08:00
Johnny bcb8843245 chore: implement stringify markdown nodes endpoint (#3688) 2024-07-14 20:46:57 +08:00
Johnny 7c9e54afbd chore: update disallow public visibility setting name (#3687) 2024-07-14 20:23:19 +08:00
Steven 6825c1def4 fix: delete access token when sign out 2024-07-13 11:18:29 +08:00
Steven f374169a2b feat: implement html element renderer 2024-07-13 11:06:51 +08:00
Johnny 4ff3b89a8e chore: update i18n from Weblate (#3680)
* Added translation using Weblate (Catalan)

* Translated using Weblate (Catalan)

Currently translated at 25.0% (65 of 260 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/ca/

* Translated using Weblate (French)

Currently translated at 100.0% (260 of 260 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/fr/

---------

Co-authored-by: Carles Mateu <carlesm@carlesm.com>
Co-authored-by: Ezmana <ezmana.land@gmail.com>
2024-07-12 22:22:59 +08:00
Johnny 5a196e2f1a feat: update gomark version (#3679) 2024-07-12 22:17:03 +08:00
Steven 9449886d60 chore: fix linter 2024-07-12 08:36:55 +08:00
Steven 457cf92cc1 feat(api): implement get resource by uid 2024-07-11 23:43:44 +08:00
Steven 1ab2c89408 feat(api): implement get memo by uid 2024-07-11 23:31:50 +08:00
dependabot[bot] 34e938a1e3 chore: bump typescript from 5.5.2 to 5.5.3 in /web (#3664)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.5.2 to 5.5.3.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.5.2...v5.5.3)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 23:00:45 +08:00
dependabot[bot] b265445068 chore: bump golang.org/x/net from 0.26.0 to 0.27.0 (#3669)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.26.0 to 0.27.0.
- [Commits](https://github.com/golang/net/compare/v0.26.0...v0.27.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 22:59:52 +08:00
dependabot[bot] 37d13b5cbc chore: bump github.com/aws/aws-sdk-go-v2/config from 1.27.23 to 1.27.24 (#3667)
Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.27.23 to 1.27.24.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.27.23...config/v1.27.24)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/config
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 22:59:45 +08:00
dependabot[bot] fb4fdb326a chore: bump golang.org/x/crypto from 0.24.0 to 0.25.0 (#3668)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.24.0 to 0.25.0.
- [Commits](https://github.com/golang/crypto/compare/v0.24.0...v0.25.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 22:59:26 +08:00
dependabot[bot] 5623a0f065 chore: bump @typescript-eslint/parser from 7.14.1 to 7.16.0 in /web (#3672)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 7.14.1 to 7.16.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.16.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 22:59:12 +08:00
dependabot[bot] b220f14bec chore: bump katex from 0.16.10 to 0.16.11 in /web (#3663)
Bumps [katex](https://github.com/KaTeX/KaTeX) from 0.16.10 to 0.16.11.
- [Release notes](https://github.com/KaTeX/KaTeX/releases)
- [Changelog](https://github.com/KaTeX/KaTeX/blob/main/CHANGELOG.md)
- [Commits](https://github.com/KaTeX/KaTeX/compare/v0.16.10...v0.16.11)

---
updated-dependencies:
- dependency-name: katex
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 22:59:03 +08:00
dependabot[bot] 3e4b33fb28 chore: bump highlight.js from 11.9.0 to 11.10.0 in /web (#3665)
Bumps [highlight.js](https://github.com/highlightjs/highlight.js) from 11.9.0 to 11.10.0.
- [Release notes](https://github.com/highlightjs/highlight.js/releases)
- [Changelog](https://github.com/highlightjs/highlight.js/blob/main/CHANGES.md)
- [Commits](https://github.com/highlightjs/highlight.js/compare/11.9.0...11.10.0)

---
updated-dependencies:
- dependency-name: highlight.js
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 18:13:36 +08:00
dependabot[bot] 08a43d0faa chore: bump @typescript-eslint/eslint-plugin from 7.14.1 to 7.15.0 in /web (#3666)
chore: bump @typescript-eslint/eslint-plugin in /web

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 7.14.1 to 7.15.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.15.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 18:13:24 +08:00
dependabot[bot] 36795abacc chore: bump golang.org/x/mod from 0.18.0 to 0.19.0 (#3670)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.18.0 to 0.19.0.
- [Commits](https://github.com/golang/mod/compare/v0.18.0...v0.19.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 18:12:49 +08:00
dependabot[bot] 9f7295a4ee chore: bump github.com/aws/aws-sdk-go-v2/credentials from 1.17.23 to 1.17.24 (#3671)
chore: bump github.com/aws/aws-sdk-go-v2/credentials

Bumps [github.com/aws/aws-sdk-go-v2/credentials](https://github.com/aws/aws-sdk-go-v2) from 1.17.23 to 1.17.24.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/credentials/v1.17.23...credentials/v1.17.24)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/credentials
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 18:12:39 +08:00
Steven 626c972d9c chore: add properties to memo detail sidebar 2024-07-08 23:14:00 +08:00
Johnny 5e3ed201b4 fix: update mask of update memo (#3659) 2024-07-08 08:50:38 +08:00
Johnny eccddb1154 chore: add create time to memo detail sidebar (#3657) 2024-07-07 23:09:48 +08:00
dependabot[bot] a681b8e205 chore: bump github.com/aws/aws-sdk-go-v2/config from 1.27.21 to 1.27.23 (#3640)
Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.27.21 to 1.27.23.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.27.21...config/v1.27.23)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/config
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-06 16:48:24 +08:00
dependabot[bot] 881809926e chore: bump github.com/aws/aws-sdk-go-v2/feature/s3/manager from 1.17.1 to 1.17.4 (#3649)
chore: bump github.com/aws/aws-sdk-go-v2/feature/s3/manager

Bumps [github.com/aws/aws-sdk-go-v2/feature/s3/manager](https://github.com/aws/aws-sdk-go-v2) from 1.17.1 to 1.17.4.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/v1.17.4/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/v1.17.1...v1.17.4)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/feature/s3/manager
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-06 16:48:15 +08:00
Johnny a98d16bcaa chore: update bug report template fields (#3656) 2024-07-06 14:16:44 +08:00
dependabot[bot] 46ca5bb8e7 chore: bump github.com/aws/aws-sdk-go-v2/credentials from 1.17.21 to 1.17.23 (#3637)
chore: bump github.com/aws/aws-sdk-go-v2/credentials

Bumps [github.com/aws/aws-sdk-go-v2/credentials](https://github.com/aws/aws-sdk-go-v2) from 1.17.21 to 1.17.23.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/credentials/v1.17.21...credentials/v1.17.23)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/credentials
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-03 11:44:04 +08:00
dependabot[bot] b1f12d54a0 chore: bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.56.1 to 1.57.1 (#3644)
chore: bump github.com/aws/aws-sdk-go-v2/service/s3

Bumps [github.com/aws/aws-sdk-go-v2/service/s3](https://github.com/aws/aws-sdk-go-v2) from 1.56.1 to 1.57.1.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.56.1...service/s3/v1.57.1)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/service/s3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-02 23:15:39 +08:00
Johnny 4d2b953e23 chore: add enable link preview setting to memo related settings (#3647) 2024-07-02 22:00:38 +08:00
dependabot[bot] b57b6bae34 chore: bump postcss from 8.4.38 to 8.4.39 in /web (#3642)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.38 to 8.4.39.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.38...8.4.39)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-02 08:25:51 +08:00
dependabot[bot] 90c67d5592 chore: bump @typescript-eslint/eslint-plugin from 7.13.1 to 7.14.1 in /web (#3641)
chore: bump @typescript-eslint/eslint-plugin in /web

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 7.13.1 to 7.14.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.14.1/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-02 08:25:38 +08:00
dependabot[bot] 4655c99c4e chore: bump @bufbuild/buf from 1.33.0 to 1.34.0 in /web (#3639)
Bumps [@bufbuild/buf](https://github.com/bufbuild/buf) from 1.33.0 to 1.34.0.
- [Release notes](https://github.com/bufbuild/buf/releases)
- [Changelog](https://github.com/bufbuild/buf/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bufbuild/buf/compare/v1.33.0...v1.34.0)

---
updated-dependencies:
- dependency-name: "@bufbuild/buf"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-02 08:25:24 +08:00
dependabot[bot] 68e924e48c chore: bump @types/node from 20.14.2 to 20.14.9 in /web (#3638)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.14.2 to 20.14.9.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-02 08:25:13 +08:00
dependabot[bot] ee61bc7861 chore: bump react-router-dom from 6.23.1 to 6.24.0 in /web (#3636)
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.23.1 to 6.24.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.24.0/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-02 08:25:02 +08:00
Steven 3782d412f5 chore: upgrade gomark 2024-07-01 23:28:36 +08:00
dependabot[bot] de754079cc chore: bump zustand from 4.5.2 to 4.5.4 in /web (#3632)
Bumps [zustand](https://github.com/pmndrs/zustand) from 4.5.2 to 4.5.4.
- [Release notes](https://github.com/pmndrs/zustand/releases)
- [Commits](https://github.com/pmndrs/zustand/compare/v4.5.2...v4.5.4)

---
updated-dependencies:
- dependency-name: zustand
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 23:27:41 +08:00
dependabot[bot] df0dcb553b chore: bump typescript from 5.4.5 to 5.5.2 in /web (#3633)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.4.5 to 5.5.2.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.4.5...v5.5.2)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 23:27:33 +08:00
dependabot[bot] 584d73659e chore: bump @reduxjs/toolkit from 2.2.5 to 2.2.6 in /web (#3634)
Bumps [@reduxjs/toolkit](https://github.com/reduxjs/redux-toolkit) from 2.2.5 to 2.2.6.
- [Release notes](https://github.com/reduxjs/redux-toolkit/releases)
- [Commits](https://github.com/reduxjs/redux-toolkit/compare/v2.2.5...v2.2.6)

---
updated-dependencies:
- dependency-name: "@reduxjs/toolkit"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 23:27:22 +08:00
dependabot[bot] 122710801a chore: bump protobufjs from 7.3.0 to 7.3.2 in /web (#3631)
Bumps [protobufjs](https://github.com/protobufjs/protobuf.js) from 7.3.0 to 7.3.2.
- [Release notes](https://github.com/protobufjs/protobuf.js/releases)
- [Changelog](https://github.com/protobufjs/protobuf.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/protobufjs/protobuf.js/compare/protobufjs-v7.3.0...protobufjs-v7.3.2)

---
updated-dependencies:
- dependency-name: protobufjs
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 23:24:33 +08:00
Steven 1ad5d9bf0a chore: update confirm dialog 2024-07-01 23:22:39 +08:00
dependabot[bot] 4a3afffeef chore: bump lucide-react from 0.396.0 to 0.399.0 in /web (#3630)
Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 0.396.0 to 0.399.0.
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.399.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 23:13:52 +08:00
Steven 291b815653 feat: implement memo detail sidebar 2024-07-01 23:06:10 +08:00
Steven 05c6edfe2f chore: tweak auth pages 2024-06-26 22:11:18 +08:00
dependabot[bot] 6325b3eef9 chore: bump @types/uuid from 9.0.8 to 10.0.0 in /web (#3616)
Bumps [@types/uuid](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/uuid) from 9.0.8 to 10.0.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/uuid)

---
updated-dependencies:
- dependency-name: "@types/uuid"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-26 08:11:39 +08:00
dependabot[bot] 80c57692e9 chore: bump github.com/aws/aws-sdk-go-v2/feature/s3/manager from 1.16.24 to 1.17.1 (#3617)
chore: bump github.com/aws/aws-sdk-go-v2/feature/s3/manager

Bumps [github.com/aws/aws-sdk-go-v2/feature/s3/manager](https://github.com/aws/aws-sdk-go-v2) from 1.16.24 to 1.17.1.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/v1.17.1/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/ram/v1.16.24...v1.17.1)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/feature/s3/manager
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-26 08:11:29 +08:00
dependabot[bot] 5828632613 chore: bump @typescript-eslint/parser from 7.13.0 to 7.14.1 in /web (#3620)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 7.13.0 to 7.14.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.14.1/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-26 08:11:18 +08:00
Roman Lomonosov b923988fd6 fix: can't change memo UID (#3621) 2024-06-26 07:18:00 +08:00
dependabot[bot] 9b090d909e chore: bump github.com/aws/aws-sdk-go-v2/config from 1.27.18 to 1.27.21 (#3612)
Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.27.18 to 1.27.21.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.27.18...config/v1.27.21)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/config
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-25 22:31:27 +08:00
dependabot[bot] 6abc272709 chore: bump lucide-react from 0.395.0 to 0.396.0 in /web (#3614)
Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 0.395.0 to 0.396.0.
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.396.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-25 22:31:12 +08:00
dependabot[bot] 3752e7b638 chore: bump eslint-plugin-react from 7.34.2 to 7.34.3 in /web (#3618)
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.34.2 to 7.34.3.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.34.2...v7.34.3)

---
updated-dependencies:
- dependency-name: eslint-plugin-react
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-25 22:30:56 +08:00
dependabot[bot] 9ba2adb4c4 chore: bump @typescript-eslint/eslint-plugin from 7.13.0 to 7.13.1 in /web (#3619)
chore: bump @typescript-eslint/eslint-plugin in /web

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 7.13.0 to 7.13.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.13.1/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-25 22:30:39 +08:00
Zeng1998 4f4fe9b26b fix: add charset=utf-8 for text content resources (#3608)
Co-authored-by: = <=>
2024-06-24 23:01:19 +08:00
Johnny ca463891f2 chore: fix linter warning for slog (#3604) 2024-06-22 16:39:40 +08:00
Mudkip e33244e64b fix: PATCH requests on /api/v1 not working (#3600)
* fix: PATCH requests on /api/v1

* Update server/server.go

---------

Co-authored-by: boojack <stevenlgtm@gmail.com>
2024-06-21 18:16:55 +08:00
steven 33cd3ed8e3 chore: bump version 2024-06-21 18:16:35 +08:00
Ryo a09c669865 fix: allow cors for grpc gateway handler (#3574) 2024-06-21 18:13:59 +08:00
dependabot[bot] 759337996f chore: bump lucide-react from 0.383.0 to 0.395.0 in /web (#3584)
Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 0.383.0 to 0.395.0.
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.395.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 22:11:05 +08:00
dependabot[bot] cee1d4b1ed chore: bump @typescript-eslint/eslint-plugin from 7.12.0 to 7.13.0 in /web (#3585)
chore: bump @typescript-eslint/eslint-plugin in /web

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 7.12.0 to 7.13.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.13.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 22:10:51 +08:00
dependabot[bot] 77b92f675a chore: bump @bufbuild/buf from 1.32.2 to 1.33.0 in /web (#3586)
Bumps [@bufbuild/buf](https://github.com/bufbuild/buf) from 1.32.2 to 1.33.0.
- [Release notes](https://github.com/bufbuild/buf/releases)
- [Changelog](https://github.com/bufbuild/buf/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bufbuild/buf/compare/v1.32.2...v1.33.0)

---
updated-dependencies:
- dependency-name: "@bufbuild/buf"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 22:10:38 +08:00
dependabot[bot] 795700b161 chore: bump vite from 5.2.13 to 5.3.1 in /web (#3587)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.2.13 to 5.3.1.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.3.1/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 22:10:28 +08:00
dependabot[bot] 513ff9d2b0 chore: bump prettier from 3.3.1 to 3.3.2 in /web (#3588)
Bumps [prettier](https://github.com/prettier/prettier) from 3.3.1 to 3.3.2.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.3.1...3.3.2)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 22:10:10 +08:00
dependabot[bot] 84692404e1 chore: bump golang.org/x/oauth2 from 0.20.0 to 0.21.0 (#3583)
Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.20.0 to 0.21.0.
- [Commits](https://github.com/golang/oauth2/compare/v0.20.0...v0.21.0)

---
updated-dependencies:
- dependency-name: golang.org/x/oauth2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 22:09:34 +08:00
dependabot[bot] d5d0d14728 chore: bump github.com/gorilla/feeds from 1.1.2 to 1.2.0 (#3582)
Bumps [github.com/gorilla/feeds](https://github.com/gorilla/feeds) from 1.1.2 to 1.2.0.
- [Release notes](https://github.com/gorilla/feeds/releases)
- [Commits](https://github.com/gorilla/feeds/compare/v1.1.2...v1.2.0)

---
updated-dependencies:
- dependency-name: github.com/gorilla/feeds
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 22:09:02 +08:00
dependabot[bot] 9bec88eedc chore: bump github.com/spf13/cobra from 1.8.0 to 1.8.1 (#3581)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 22:08:53 +08:00
dependabot[bot] 1bf6db8154 chore: bump google.golang.org/protobuf from 1.34.1 to 1.34.2 (#3580)
Bumps google.golang.org/protobuf from 1.34.1 to 1.34.2.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 22:08:42 +08:00
dependabot[bot] 5fb649c3c3 chore: bump modernc.org/sqlite from 1.30.0 to 1.30.1 (#3579)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.30.0 to 1.30.1.
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.30.0...v1.30.1)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 22:08:30 +08:00
dependabot[bot] b911e98e37 chore: bump docker/build-push-action from 5 to 6 (#3578)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5 to 6.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 22:08:20 +08:00
Steven 0501f5198a chore: buf format 2024-06-19 22:07:51 +08:00
Johnny 736637a362 feat: add public flag instead of system setting to enable signup (#3589) 2024-06-19 22:03:12 +08:00
Steven 387bf48fc8 chore: update s3 config 2024-06-11 22:05:28 +08:00
dependabot[bot] e9d027ca3e chore: bump @typescript-eslint/parser from 7.12.0 to 7.13.0 in /web (#3558)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 7.12.0 to 7.13.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.13.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-11 10:48:03 +08:00
dependabot[bot] 0c378ec291 chore: bump github.com/aws/aws-sdk-go-v2/feature/s3/manager from 1.16.21 to 1.16.24 (#3547)
chore: bump github.com/aws/aws-sdk-go-v2/feature/s3/manager

Bumps [github.com/aws/aws-sdk-go-v2/feature/s3/manager](https://github.com/aws/aws-sdk-go-v2) from 1.16.21 to 1.16.24.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/ram/v1.16.21...service/ram/v1.16.24)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/feature/s3/manager
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-11 09:01:47 +08:00
dependabot[bot] 5836bf5a72 chore: bump golang.org/x/net from 0.25.0 to 0.26.0 (#3549)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.25.0 to 0.26.0.
- [Commits](https://github.com/golang/net/compare/v0.25.0...v0.26.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-11 08:59:43 +08:00
dependabot[bot] 7aba6bef21 chore: bump github.com/aws/aws-sdk-go-v2/config from 1.27.16 to 1.27.18 (#3550)
Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.27.16 to 1.27.18.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.27.16...config/v1.27.18)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/config
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-11 08:59:32 +08:00
dependabot[bot] f31a53caa7 chore: bump uuid from 9.0.1 to 10.0.0 in /web (#3556)
Bumps [uuid](https://github.com/uuidjs/uuid) from 9.0.1 to 10.0.0.
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v9.0.1...v10.0.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-11 08:59:08 +08:00
dependabot[bot] 28624514d1 chore: bump goreleaser/goreleaser-action from 5 to 6 (#3551)
Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 5 to 6.
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: goreleaser/goreleaser-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-10 23:35:09 +08:00
dependabot[bot] b42979edcc chore: bump golang.org/x/mod from 0.17.0 to 0.18.0 (#3548)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.17.0 to 0.18.0.
- [Commits](https://github.com/golang/mod/compare/v0.17.0...v0.18.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-10 23:34:45 +08:00
dependabot[bot] bc1b095812 chore: bump github.com/aws/aws-sdk-go-v2 from 1.27.0 to 1.27.2 (#3546)
Bumps [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) from 1.27.0 to 1.27.2.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/v1.27.0...v1.27.2)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-10 23:34:24 +08:00
Ryo 2c819ace4e feat: return not found error instead internal error for local file not exist (#3543)
* chore: add `/.vscode` to `.gitignore`

* feat: return not found instead of internal for resource

* feat: check file not exist only if error not nil
2024-06-10 14:11:28 +08:00
andrigamerita e4a09c407c feat: write memo visibility in file names when exporting (#3538)
When using the dedicated feature in Memos' user settings to export all memos to Markdown files inside a ZIP folder, the output doesn't feature any kind of distinction for memos by their set visibility.
While this is not a big issue for personal backups, it can reveal itself problematic if exporting the data to share it with other people, or maybe deploy to a static site generator, because there is nothing in the files that distinguishes public memos from private or workspace-restricted ones.

This pull request simply modifies the ExportMemos server function, to add the Visibility status to the end of every exported file name inside the ZIP, right after the date (which is left intact). For example, the file for a public memo would now be called: `YYYY-MM-DDThh:mm:ss+hh:mm-PUBLIC.md`.

An alternative solution would have been to write this information in a YAML header at the beginning of every Markdown file, but, since those are not used anywhere else in the software, I decided to stick with what is already used for export metadata, the filename.
2024-06-09 19:22:13 +08:00
Steven da603053f2 chore: add th locale 2024-06-09 07:46:03 +08:00
boojack edda6ea4bd chore: update i18n with Weblate (#3540)
* Added translation using Weblate (Thai)

* Translated using Weblate (Thai)

Currently translated at 100.0% (260 of 260 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/th/

---------

Co-authored-by: NeneNeko <lennon.rin@gmail.com>
2024-06-09 07:43:59 +08:00
boojack 7b7ecc2daa chore: update i18n with Weblate (#3533)
* Translated using Weblate (Japanese)

Currently translated at 100.0% (260 of 260 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/ja/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (260 of 260 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hans/

---------

Co-authored-by: Somme4096 <somme4096@gmail.com>
Co-authored-by: Yuri <isyuricunha@duck.com>
2024-06-07 05:10:12 +08:00
Steven fa4521e0c5 chore: update gomark source 2024-06-06 23:09:13 +08:00
Steven 076aa2f8aa chore: remove memo filter 2024-06-05 23:18:14 +08:00
Steven f33571fec6 feat: update webhook request payload 2024-06-05 20:53:20 +08:00
Steven d159975994 chore: update statistics view 2024-06-05 20:46:35 +08:00
Martin MacDonald aec5927d31 feat: add hook for generating different date times (#3524)
* Added hook for generating different date times

* Fixed up logic to destructure the datetime params

---------

Co-authored-by: Martin MacDonald <martinmacdonald@Martins-MacBook-Pro.local>
2024-06-05 19:52:07 +08:00
Steven 7375b87967 chore: tweak tooltips 2024-06-05 08:51:04 +08:00
Steven 54539aa047 chore: tweak seed data 2024-06-05 08:48:46 +08:00
Steven d0ac866eb1 chore: update statistics view 2024-06-05 08:48:40 +08:00
Steven 797f5a123c feat: add HasIncompleteTasks to memo property 2024-06-05 08:39:56 +08:00
Martin MacDonald f0817f2762 chore: update fetch tags args (#3515)
* Centralised the logic for filters to apply to tagging and updated components to pass in those params needed

* Fixed linting issue

* Split out params from options

* Fixed linting errors

---------

Co-authored-by: Martin MacDonald <martinmacdonald@Martins-MacBook-Pro.local>
2024-06-05 08:06:01 +08:00
Martin MacDonald 2ebd5c64bd fix: copy by reference (#3517)
* Fixed issue with copy by reference

* Fixed linting issues

---------

Co-authored-by: Martin MacDonald <martinmacdonald@Martins-MacBook-Pro.local>
2024-06-05 08:04:50 +08:00
Martin MacDonald 0977acbc7f fix: update uniqby key (#3518)
* Used correct uniqby key

* Put back filter

---------

Co-authored-by: Martin MacDonald <martinmacdonald@Martins-MacBook-Pro.local>
2024-06-05 08:03:56 +08:00
Steven e8ed437ca3 fix: purify html before render 2024-06-04 23:10:54 +08:00
Steven 2b49025f4a chore: update memo filters 2024-06-04 23:03:51 +08:00
Steven bddfe00b5c chore: bump version 2024-06-04 20:51:53 +08:00
dependabot[bot] aafddac65f chore: bump modernc.org/sqlite from 1.29.10 to 1.30.0 (#3510)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.29.10 to 1.30.0.
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.29.10...v1.30.0)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-04 07:18:07 +08:00
dependabot[bot] b4113581b3 chore: bump github.com/spf13/viper from 1.18.2 to 1.19.0 (#3511)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.18.2 to 1.19.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.18.2...v1.19.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-04 07:17:40 +08:00
Steven e40b39033f chore: fix compact default value 2024-06-04 07:17:16 +08:00
Michael 616f306efe chore: update i18n with Weblate (#3509)
* Translated using Weblate (French)

Currently translated at 100.0% (260 of 260 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/fr/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (260 of 260 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hant/

---------

Co-authored-by: Ezmana <ezmana.land@gmail.com>
Co-authored-by: ti777777 <ti12272198686@yahoo.com.tw>
2024-06-03 23:57:59 +08:00
Steven 370ac32344 chore: regenerate pnpm-local.yaml 2024-06-03 23:51:29 +08:00
Steven 20b8a97a88 chore: retire outdate scripts 2024-06-03 23:50:59 +08:00
dependabot[bot] be6fc1ca4d chore: bump lucide-react from 0.378.0 to 0.383.0 in /web (#3507)
Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 0.378.0 to 0.383.0.
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.383.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-03 23:40:00 +08:00
dependabot[bot] c41d53b4b6 chore: bump @types/node from 20.12.12 to 20.14.0 in /web (#3504)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.12.12 to 20.14.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-03 23:38:03 +08:00
Martin MacDonald 760dee4159 feat: delete memo comments when memo is deleted (#3491)
* Added logic to delete memo relation and memo comments recursively when a memo is deleted

* Fixed logic to only target comment types and added removing of broken references

* Fixed linting errors

---------

Co-authored-by: Martin MacDonald <martinmacdonald@Martins-MacBook-Pro.local>
2024-06-03 19:50:13 +08:00
Steven 08d37acffa chore: tweak readme 2024-06-02 14:31:54 +08:00
Michael c484b09c4c chore: update i18n with Weblate (#3499)
* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (260 of 260 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/pt_BR/

* Translated using Weblate (Turkish)

Currently translated at 100.0% (260 of 260 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/tr/

---------

Co-authored-by: Lincoln Nogueira <lincolnthalles@users.noreply.github.com>
Co-authored-by: Oğuz Han <h4n.3545@gmail.com>
2024-06-02 14:06:05 +08:00
Steven 2e0d5412b4 chore: tweak webhook payload 2024-06-01 23:46:00 +08:00
Steven 8c0bee3840 chore: tweak refresh button 2024-06-01 23:27:35 +08:00
Steven 78f6064530 chore: tweak goreleaser action 2024-06-01 10:04:39 +08:00
Steven d7be24a0e1 chore: tweak goreleaser 2024-06-01 09:59:52 +08:00
Steven 5493f5f14e chore: regenerate dependencies 2024-06-01 09:58:47 +08:00
Steven 1dc686fa6c chore: update build artifacts 2024-06-01 09:49:47 +08:00
Steven 4e54ec38ff chore: update memo related settings 2024-06-01 09:05:09 +08:00
Steven 0657a1ef5b chore: fix memo editor cache 2024-06-01 08:48:54 +08:00
Michael 48546f05b3 chore: update i18n with Weblate (#3492)
* Translated using Weblate (Turkish)

Currently translated at 100.0% (259 of 259 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/tr/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (259 of 259 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hans/

* Apply suggestions from code review

---------

Co-authored-by: Oğuz Han <h4n.3545@gmail.com>
Co-authored-by: Coisini-H <2972186196@qq.com>
2024-06-01 08:42:01 +08:00
Steven 08a9291964 chore: update timeline filters 2024-05-31 09:06:03 +08:00
Steven 98f2cf6397 chore: tweak linter 2024-05-30 07:23:16 +08:00
Steven b1cc1255bb chore: tweak seed data 2024-05-30 07:21:10 +08:00
Steven b215426623 chore: update general setting 2024-05-30 07:19:38 +08:00
Steven 423861d48c fix: create idp in postgres 2024-05-29 23:22:20 +08:00
Michael f2b708c1b8 chore: update i18n with Weblate (#3483)
* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (257 of 257 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/pt_BR/

* Translated using Weblate (Turkish)

Currently translated at 100.0% (257 of 257 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/tr/

---------

Co-authored-by: Lincoln Nogueira <lincolnthalles@users.noreply.github.com>
Co-authored-by: Ramazan SANCAR <ramazansancar4545@gmail.com>
2024-05-29 23:20:11 +08:00
Steven 0f4b7c5f4a feat: add more memo settings 2024-05-29 23:17:53 +08:00
Steven 1894ef161f chore: fix content search 2024-05-29 22:04:37 +08:00
Michael 98eb57b370 chore: update translation files (#3481)
Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/

Co-authored-by: Hosted Weblate <hosted@weblate.org>
2024-05-29 08:05:14 +08:00
Steven cf70867470 chore: update tag section 2024-05-29 08:03:01 +08:00
Steven 719c22fef8 chore: update i18n 2024-05-29 07:39:16 +08:00
Steven 6010139291 chore: remove unused 2024-05-29 07:36:51 +08:00
Steven cbf556fee5 chore: fix memo cache key 2024-05-28 23:23:23 +08:00
Steven 93e848d170 chore: tweak root redirector 2024-05-28 19:59:29 +08:00
Steven 60b5b4815d chore: fix imports 2024-05-28 09:10:08 +08:00
dependabot[bot] 98bd4568e7 chore: bump github.com/aws/aws-sdk-go-v2/feature/s3/manager from 1.16.20 to 1.16.21 (#3469)
chore: bump github.com/aws/aws-sdk-go-v2/feature/s3/manager

Bumps [github.com/aws/aws-sdk-go-v2/feature/s3/manager](https://github.com/aws/aws-sdk-go-v2) from 1.16.20 to 1.16.21.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/ram/v1.16.20...service/ram/v1.16.21)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/feature/s3/manager
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-28 09:07:42 +08:00
dependabot[bot] c24225fd89 chore: bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.54.2 to 1.54.3 (#3471)
chore: bump github.com/aws/aws-sdk-go-v2/service/s3

Bumps [github.com/aws/aws-sdk-go-v2/service/s3](https://github.com/aws/aws-sdk-go-v2) from 1.54.2 to 1.54.3.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.54.2...service/s3/v1.54.3)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/service/s3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-28 09:06:53 +08:00
dependabot[bot] 5ea04ca596 chore: bump github.com/aws/aws-sdk-go-v2/config from 1.27.15 to 1.27.16 (#3472)
Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.27.15 to 1.27.16.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.27.15...config/v1.27.16)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/config
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-28 09:06:45 +08:00
Steven 0275a65781 chore: add last visited page 2024-05-28 09:05:59 +08:00
dependabot[bot] 239f28c1e0 chore: bump @vitejs/plugin-react from 4.2.1 to 4.3.0 in /web (#3467)
Bumps [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) from 4.2.1 to 4.3.0.
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/v4.3.0/packages/plugin-react)

---
updated-dependencies:
- dependency-name: "@vitejs/plugin-react"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-28 06:07:43 +08:00
dependabot[bot] f78114da43 chore: bump github.com/aws/aws-sdk-go-v2/credentials from 1.17.15 to 1.17.16 (#3468)
chore: bump github.com/aws/aws-sdk-go-v2/credentials

Bumps [github.com/aws/aws-sdk-go-v2/credentials](https://github.com/aws/aws-sdk-go-v2) from 1.17.15 to 1.17.16.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/credentials/v1.17.15...credentials/v1.17.16)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/credentials
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-27 23:37:05 +08:00
dependabot[bot] 75e76e1331 chore: bump @types/react from 18.3.2 to 18.3.3 in /web (#3466)
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.3.2 to 18.3.3.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-27 23:36:53 +08:00
dependabot[bot] 9f83be1d48 chore: bump @typescript-eslint/parser from 7.9.0 to 7.10.0 in /web (#3465)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 7.9.0 to 7.10.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.10.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-27 23:36:43 +08:00
dependabot[bot] 250bdff4c5 chore: bump @bufbuild/buf from 1.32.0 to 1.32.1 in /web (#3464)
Bumps [@bufbuild/buf](https://github.com/bufbuild/buf) from 1.32.0 to 1.32.1.
- [Release notes](https://github.com/bufbuild/buf/releases)
- [Changelog](https://github.com/bufbuild/buf/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bufbuild/buf/compare/v1.32.0...v1.32.1)

---
updated-dependencies:
- dependency-name: "@bufbuild/buf"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-27 23:36:33 +08:00
dependabot[bot] b8d4720e5e chore: bump i18next from 23.11.4 to 23.11.5 in /web (#3463)
Bumps [i18next](https://github.com/i18next/i18next) from 23.11.4 to 23.11.5.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v23.11.4...v23.11.5)

---
updated-dependencies:
- dependency-name: i18next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-27 23:36:24 +08:00
dependabot[bot] 3e11adc15b chore: bump modernc.org/sqlite from 1.29.9 to 1.29.10 (#3470)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.29.9 to 1.29.10.
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.29.9...v1.29.10)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-27 23:36:09 +08:00
Steven e475f00ba1 chore: tweak seed data 2024-05-27 23:32:08 +08:00
Steven f0e5a72271 feat: update search memo filter 2024-05-27 23:25:25 +08:00
Steven ba0876a563 feat: update statistics view 2024-05-27 20:04:07 +08:00
Steven a423dac12c chore: tweak memo property 2024-05-27 19:43:57 +08:00
Steven 2b4c2b23b2 chore: tweak linter 2024-05-26 21:39:34 +08:00
Ryo 784fcbb698 feat: add panic recovery middleware for grpc and echo server (#3459) 2024-05-26 21:38:59 +08:00
Steven 1ccfa81cf3 chore: tweak common function 2024-05-26 11:02:23 +08:00
Steven a177800e83 chore: tweak error message 2024-05-25 11:05:35 +08:00
Steven d39cacf1f9 chore: tweak readme 2024-05-25 11:01:54 +08:00
Michael 8233a6ba1c chore: translated using Weblate (Marathi) (#3451)
Translated using Weblate (Marathi)

Currently translated at 100.0% (255 of 255 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/mr/

Co-authored-by: quaintdev <accounts@rohanrd.xyz>
2024-05-25 05:44:20 +08:00
quaintdev af71ac49bb feat(i18n): added marathi(mr) translations (#3449) 2024-05-25 05:43:37 +08:00
Steven 7d6dbb9487 fix: link hover handler 2024-05-22 23:25:04 +08:00
Steven 4070998f9f chore: fix linter 2024-05-22 21:55:13 +08:00
Steven d3235f75f3 chore: tweak memo definition 2024-05-22 21:52:41 +08:00
Steven e0600388b2 chore: implement memo property endpoint 2024-05-22 09:21:12 +08:00
Steven 16d3de63c2 chore: update resource binary endpoint 2024-05-21 21:25:21 +08:00
Michael 2c50d3469e chore: update i18n with weblate (#3435)
* Translated using Weblate (Ukrainian)

Currently translated at 58.0% (148 of 255 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/uk/

* Translated using Weblate (Ukrainian)

Currently translated at 58.0% (148 of 255 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/uk/

---------

Co-authored-by: LibreTranslate <noreply-mt-libretranslate@weblate.org>
Co-authored-by: Gregory Malets <gregory.malets@gmail.com>
2024-05-21 20:36:28 +08:00
Steven b5d2ff6e28 chore: fix linter 2024-05-21 00:08:57 +08:00
dependabot[bot] 3616194d03 chore: bump github.com/aws/aws-sdk-go-v2/feature/s3/manager from 1.16.17 to 1.16.20 (#3415)
chore: bump github.com/aws/aws-sdk-go-v2/feature/s3/manager

Bumps [github.com/aws/aws-sdk-go-v2/feature/s3/manager](https://github.com/aws/aws-sdk-go-v2) from 1.16.17 to 1.16.20.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/ram/v1.16.17...service/ram/v1.16.20)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/feature/s3/manager
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-21 00:04:02 +08:00
Steven 811c0757c4 chore: fix linter 2024-05-21 00:03:30 +08:00
dependabot[bot] 464b00ea0b chore: bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.53.2 to 1.54.2 (#3416)
chore: bump github.com/aws/aws-sdk-go-v2/service/s3

Bumps [github.com/aws/aws-sdk-go-v2/service/s3](https://github.com/aws/aws-sdk-go-v2) from 1.53.2 to 1.54.2.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.53.2...service/s3/v1.54.2)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/service/s3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-21 00:02:17 +08:00
dependabot[bot] a1e610fd8e chore: bump github.com/grpc-ecosystem/grpc-gateway/v2 from 2.19.1 to 2.20.0 (#3417)
chore: bump github.com/grpc-ecosystem/grpc-gateway/v2

Bumps [github.com/grpc-ecosystem/grpc-gateway/v2](https://github.com/grpc-ecosystem/grpc-gateway) from 2.19.1 to 2.20.0.
- [Release notes](https://github.com/grpc-ecosystem/grpc-gateway/releases)
- [Changelog](https://github.com/grpc-ecosystem/grpc-gateway/blob/main/.goreleaser.yml)
- [Commits](https://github.com/grpc-ecosystem/grpc-gateway/compare/v2.19.1...v2.20.0)

---
updated-dependencies:
- dependency-name: github.com/grpc-ecosystem/grpc-gateway/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-21 00:02:08 +08:00
dependabot[bot] e813853624 chore: bump google.golang.org/grpc from 1.63.2 to 1.64.0 (#3418)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.63.2 to 1.64.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.63.2...v1.64.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-21 00:00:52 +08:00
dependabot[bot] b2f9496c96 chore: bump github.com/aws/aws-sdk-go-v2/config from 1.27.13 to 1.27.15 (#3414)
Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.27.13 to 1.27.15.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.27.13...config/v1.27.15)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/config
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-21 00:00:27 +08:00
dependabot[bot] f1392e220e chore: bump @typescript-eslint/parser from 7.8.0 to 7.9.0 in /web (#3423)
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 7.8.0 to 7.9.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.9.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-21 00:00:06 +08:00
Steven f37b34544b chore: update user access token checks 2024-05-20 22:32:42 +08:00
Steven f54b05a521 chore: tweak comments 2024-05-20 22:15:51 +08:00
Steven 613c9e9416 chore: tweak version 2024-05-20 21:10:04 +08:00
Steven fb15386db2 chore: remove access token after sign out 2024-05-20 08:53:29 +08:00
Steven 21838d0aae chore: fix editor autocomplete 2024-05-20 08:49:55 +08:00
Timothy 52ebfd703c fix: update resource with id (#3405)
* chore: update presign expires

* fix: update resource with id

---------

Co-authored-by: timothy <timothy123890511@gmail.com>
2024-05-19 23:18:03 +08:00
Michael 8f6d38719f chore: update i18n from Weblate (#3399)
* Added translation using Weblate (Marathi)

* Translated using Weblate (Marathi)

Currently translated at 22.7% (58 of 255 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/mr/

---------

Co-authored-by: quaintdev <accounts@rohanrd.xyz>
2024-05-19 09:16:13 +08:00
Steven 93d06bc0f0 chore: tweak readme 2024-05-19 09:15:46 +08:00
Steven c25c57ab61 feat: support updating display time 2024-05-18 07:52:35 +08:00
Steven b0aad6f694 chore: tweak resource payload 2024-05-17 08:50:02 +08:00
Steven 0c251f9ab8 chore: fix resource delete handler 2024-05-16 21:53:12 +08:00
wzc90 0a9212f815 fix: memo find for mysql(#3387)
* Update memo.go

* Update store/db/mysql/memo.go

Co-authored-by: boojack <stevenlgtm@gmail.com>

---------

Co-authored-by: boojack <stevenlgtm@gmail.com>
2024-05-16 07:37:42 +08:00
Steven 537ae622d2 chore: filter inbox type 2024-05-15 23:22:23 +08:00
Steven 3e5e5b1f80 chore: fix search memos 2024-05-15 22:42:31 +08:00
wzc90 3ecbbf31a9 fix: migration scripts for mysql (#3384) 2024-05-15 17:34:59 +08:00
wzc90 deb1deb14c fix: resource create time format for mysql (#3381) 2024-05-15 15:51:43 +08:00
Steven c8ff3fa2ee chore: fix uploading flag 2024-05-15 09:08:34 +08:00
dependabot[bot] f67676ac43 chore: bump github.com/aws/aws-sdk-go-v2/feature/s3/manager from 1.16.15 to 1.16.17 (#3367)
chore: bump github.com/aws/aws-sdk-go-v2/feature/s3/manager

Bumps [github.com/aws/aws-sdk-go-v2/feature/s3/manager](https://github.com/aws/aws-sdk-go-v2) from 1.16.15 to 1.16.17.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/v1.16.15...service/ram/v1.16.17)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/feature/s3/manager
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-14 11:48:46 +08:00
dependabot[bot] 38c6c50681 chore: bump golangci/golangci-lint-action from 5 to 6 (#3365)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 5 to 6.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-14 11:46:44 +08:00
dependabot[bot] 74e0f5429a chore: bump pnpm/action-setup from 3.0.0 to 4.0.0 (#3366)
Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 3.0.0 to 4.0.0.
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/v3.0.0...v4.0.0)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-14 11:46:35 +08:00
dependabot[bot] 3add70714f chore: bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.53.1 to 1.53.2 (#3369)
chore: bump github.com/aws/aws-sdk-go-v2/service/s3

Bumps [github.com/aws/aws-sdk-go-v2/service/s3](https://github.com/aws/aws-sdk-go-v2) from 1.53.1 to 1.53.2.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.53.1...service/s3/v1.53.2)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/service/s3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-14 11:46:28 +08:00
dependabot[bot] 78f0ae649a chore: bump golang.org/x/net from 0.24.0 to 0.25.0 (#3370)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.24.0 to 0.25.0.
- [Commits](https://github.com/golang/net/compare/v0.24.0...v0.25.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-14 08:12:52 +08:00
Steven 9265b8e2bd chore: update tags filter 2024-05-14 08:12:25 +08:00
Steven 2317204c50 fix: list idp 2024-05-14 07:04:17 +08:00
dependabot[bot] 32abc50af4 chore: bump github.com/aws/aws-sdk-go-v2/config from 1.27.11 to 1.27.13 (#3371) 2024-05-14 06:45:14 +08:00
Steven 0c673d49ef chore: retire memo tags 2024-05-13 22:12:56 +08:00
Steven d3744ccfa3 chore: tweak seed data 2024-05-13 22:07:44 +08:00
Steven c561362d62 feat: implement memo property 2024-05-13 22:04:37 +08:00
Steven 555b4fbe32 chore: update seed data 2024-05-13 20:27:24 +08:00
Steven 8948edf654 chore: impl memo payload definition 2024-05-13 20:24:11 +08:00
Steven 31e07c083d chore: fix types 2024-05-13 20:07:56 +08:00
Steven b8763905ba chore: tweak naming 2024-05-13 20:03:04 +08:00
Michael 62705fed39 chore: translated using Weblate (French) (#3358)
Translated using Weblate (French)

Currently translated at 100.0% (255 of 255 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/fr/

Co-authored-by: Ezmana <ezmana.land@gmail.com>
2024-05-13 10:18:33 +08:00
Michael 6df3cf808e chore: update seed data 2024-05-13 10:15:30 +08:00
Michael 6c481743de chore: translated using Weblate (Turkish) (#3356)
Translated using Weblate (Turkish)

Currently translated at 100.0% (255 of 255 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/tr/

Co-authored-by: Oğuz Han <h4n.3545@gmail.com>
2024-05-13 08:10:15 +08:00
Steven 83b9ea45b9 chore: tweak dark mode styles 2024-05-13 08:09:32 +08:00
Steven f79554371d chore: retire share dialog 2024-05-13 07:57:58 +08:00
Steven eda1983964 chore: return workspace setting with default value 2024-05-12 13:32:26 +08:00
Steven cf423026a5 chore: update store cache 2024-05-12 13:19:31 +08:00
Leo LI 93e8fa4912 fix: upgrade scripts for mysql
* Fix mysql db migration

* Update store/db/mysql/migration/prod/0.22/01__memo_tags.sql

* Update store/db/mysql/migration/prod/0.22/02__memo_payload.sql

---------

Co-authored-by: boojack <stevenlgtm@gmail.com>
2024-05-12 12:34:21 +08:00
Steven bb076ce486 chore: update list workspace settings 2024-05-12 08:09:14 +08:00
Steven cf7718f8dc chore: update presign expires 2024-05-12 08:03:56 +08:00
Steven 29a22914cf fix: postgres upgrade script 2024-05-11 22:35:26 +08:00
Steven 087f60ba7b chore: fix user setting key checks 2024-05-11 21:14:21 +08:00
wzc90 d4157bcffa fix: upgrade scripts (#3347) 2024-05-11 17:43:13 +08:00
Michael ee984a8872 chore: translated using Weblate (Chinese (Simplified)) (#3345)
Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (255 of 255 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hans/

Co-authored-by: Coisini-H <2972186196@qq.com>
2024-05-11 16:00:33 +08:00
wzc90 80df8f8aed fix: upgrade scripts (#3344) 2024-05-11 15:38:14 +08:00
Michael a15025303b chore: update i18n with Weblate (#3339)
* Translated using Weblate (Japanese)

Currently translated at 100.0% (255 of 255 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/ja/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (255 of 255 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/pt_BR/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 99.6% (254 of 255 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hant/

---------

Co-authored-by: Somme4096 <somme4096@gmail.com>
Co-authored-by: Lincoln Nogueira <lincolnthalles@users.noreply.github.com>
2024-05-11 08:15:40 +08:00
Lincoln Nogueira f45f673aec chore: respect supplied listening address (#3338) 2024-05-11 06:50:22 +08:00
Steven f30599fbd2 chore: fix scripts 2024-05-10 23:23:15 +08:00
Steven 041f9b1beb chore: bump version 2024-05-10 23:02:57 +08:00
boojack 3fbc4d8539 feat: implement drag and drop for resource order in editor (#3337)
* Implement drag and drop for resource order in editor

* chore: update

* chore: update

* chore: update
2024-05-10 20:34:35 +08:00
Steven 5f207c8f0c fix: math overflow 2024-05-10 06:21:47 +08:00
Michael fe5a0fe98a chore: update i18n with Weblate (#3335)
* Translated using Weblate (Japanese)

Currently translated at 100.0% (255 of 255 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/ja/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (255 of 255 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/pt_BR/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/

---------

Co-authored-by: Somme4096 <somme4096@gmail.com>
Co-authored-by: Lincoln Nogueira <lincolnthalles@users.noreply.github.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
2024-05-09 22:40:27 +08:00
Steven 584c669068 chore: tweak max width 2024-05-09 22:10:27 +08:00
Steven 46a085f8df chore: add datepicker to timeline 2024-05-09 20:31:37 +08:00
Steven e7a788fa71 chore: tweak memo detail page 2024-05-09 20:10:13 +08:00
Steven 942052b1ea chore: update grpc options 2024-05-09 20:08:18 +08:00
Steven deae53c1f1 chore: add dayjs to fix datetime format in safari 2024-05-09 08:52:34 +08:00
Steven 1d99dad435 feat: update timeline page 2024-05-09 07:56:00 +08:00
Steven 33133ea1a3 chore: tweak searchbar styles 2024-05-08 22:50:13 +08:00
Steven a4235bb4bf chore: tweak tags style 2024-05-08 22:31:22 +08:00
Steven e0977e53f7 chore: remove unused route 2024-05-08 22:15:29 +08:00
Michael 4e12744811 chore: update translation files (#3332)
Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/

Co-authored-by: Hosted Weblate <hosted@weblate.org>
2024-05-08 21:35:19 +08:00
Steven 40a5503d45 chore: tweak i18n locales 2024-05-08 21:33:49 +08:00
Steven 16670b6b8d chore: tweak seed data 2024-05-08 20:08:53 +08:00
Michael d57e7d5a0b chore: update translation files (#3331)
Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/

Co-authored-by: Hosted Weblate <hosted@weblate.org>
2024-05-08 20:06:05 +08:00
Steven f7f4206fa1 chore: fix linter 2024-05-08 20:05:16 +08:00
Steven 292861be16 chore: retire tag store 2024-05-08 20:03:18 +08:00
Steven d0655ece53 refactor: update memo tags 2024-05-08 20:03:01 +08:00
Steven 2c270438ec chore: update default service config 2024-05-08 06:57:53 +08:00
Jerwin Arnado a9caecf479 fix: Set max height to 50vh for better viewing for larger screens. (#3330)
Feature: Set max height to 50vh for better viewing for larger screens.
2024-05-07 23:19:20 +08:00
Steven d355e2c631 chore: tweak tags section 2024-05-07 22:11:46 +08:00
Steven 4950ea1c74 fix: grpc max message size 2024-05-07 22:10:27 +08:00
Steven f9258e41a0 chore: tweak max width 2024-05-07 22:10:08 +08:00
steven e16546f80a chore: handle legacy workspace setting 2024-05-07 22:07:44 +08:00
dependabot[bot] a73f979f96 chore: bump lucide-react from 0.368.0 to 0.378.0 in /web (#3313)
Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 0.368.0 to 0.378.0.
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.378.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-07 08:53:35 +08:00
dependabot[bot] 41549a6951 chore: bump @types/node from 20.12.9 to 20.12.10 in /web (#3323)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.12.9 to 20.12.10.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-07 08:53:18 +08:00
dependabot[bot] 1e8346290c chore: bump @mui/joy from 5.0.0-beta.32 to 5.0.0-beta.36 in /web (#3317)
Bumps [@mui/joy](https://github.com/mui/material-ui/tree/HEAD/packages/mui-joy) from 5.0.0-beta.32 to 5.0.0-beta.36.
- [Release notes](https://github.com/mui/material-ui/releases)
- [Changelog](https://github.com/mui/material-ui/blob/next/CHANGELOG.md)
- [Commits](https://github.com/mui/material-ui/commits/HEAD/packages/mui-joy)

---
updated-dependencies:
- dependency-name: "@mui/joy"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-07 08:53:06 +08:00
dependabot[bot] 6a2701ee83 chore: bump react-redux from 9.1.1 to 9.1.2 in /web (#3316) 2024-05-07 05:11:26 +08:00
dependabot[bot] 68fcf250a0 chore: bump google.golang.org/protobuf from 1.33.0 to 1.34.1 (#3318) 2024-05-07 05:11:15 +08:00
dependabot[bot] 5aaf0b88fd chore: bump golang.org/x/oauth2 from 0.19.0 to 0.20.0 (#3319) 2024-05-07 05:11:04 +08:00
dependabot[bot] f9b0a2b693 chore: bump golang.org/x/crypto from 0.22.0 to 0.23.0 (#3320) 2024-05-07 05:10:52 +08:00
dependabot[bot] bf07a2cfe6 chore: bump modernc.org/sqlite from 1.29.8 to 1.29.9 (#3321) 2024-05-07 05:10:40 +08:00
Steven 776664105a chore: tweak content length limit 2024-05-06 19:12:30 +08:00
Michael b0ecb72eaa chore: translated using Weblate (Hungarian) (#3312)
Translated using Weblate (Hungarian)

Currently translated at 98.6% (299 of 303 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/hu/

Co-authored-by: Vermunds <33235701+Vermunds@users.noreply.github.com>
2024-05-06 08:03:52 +08:00
Steven af1ad2f2db chore: add memo content length limit setting 2024-05-06 08:02:39 +08:00
Nabiel Omar Syarif 56ceba2dec fix: fix deleting tag from tag lists (#3311) 2024-05-06 07:38:15 +08:00
Steven e32a585bbf chore: update resource dashboard 2024-05-06 07:37:48 +08:00
Steven dfc0889a4f chore: tweak package name 2024-05-06 07:18:54 +08:00
Michael da535c8dc6 chore: translated using Weblate (Chinese (Simplified)) (#3307)
Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (303 of 303 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hans/

Co-authored-by: Sung Kim <1740603900@qq.com>
2024-05-04 11:52:26 +08:00
Steven 10c57167cc chore: remove unused flags 2024-05-03 07:16:07 +08:00
Steven 5742f9ca13 fix: seed data 2024-05-03 07:02:08 +08:00
Steven e9831caca1 chore: remove unused field 2024-05-02 22:21:49 +08:00
Steven b23b6302de chore: tweak linter 2024-05-02 22:10:50 +08:00
Steven 74145157a0 chore: add presign background service 2024-05-02 22:08:45 +08:00
Steven 05f73a2236 chore: tweak linter 2024-05-02 21:46:47 +08:00
Steven 775b79338d chore: update object in s3 2024-05-02 21:44:17 +08:00
Steven 26545c855c refactor: implement s3 storage 2024-05-02 21:28:06 +08:00
Michael 355ea352aa chore: translated using Weblate (Chinese (Traditional)) (#3304)
Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (303 of 303 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (303 of 303 strings)


Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hant/
Translation: memos-i18n/i18n

Co-authored-by: Yuri <isyuricunha@duck.com>
2024-05-02 20:23:12 +08:00
Steven 144269fbbc chore: fix router 2024-05-01 12:44:18 +08:00
Steven 590b626052 chore: update upload resources button 2024-05-01 10:43:09 +08:00
Steven 832ad92bac chore: update gitignore 2024-05-01 10:31:26 +08:00
Steven 20dd3e17f7 chore: rename router package 2024-05-01 10:28:32 +08:00
Steven 8ae4bc95dc chore: migrate auth package 2024-05-01 10:26:46 +08:00
Steven ff175bbb7e refactor: update resource binary request handler 2024-05-01 10:23:56 +08:00
Steven 6d3d71df30 chore: update frontend dependencies 2024-04-30 22:27:20 +08:00
Steven d668794c7c chore: update build docker image actions 2024-04-30 22:21:46 +08:00
Steven 33f52320f7 fix: remove lazy import components 2024-04-30 22:06:47 +08:00
Steven 6295979592 chore: add user avatar route 2024-04-30 22:06:34 +08:00
Michael cac6f42770 fix: server matchs 2024-04-30 10:18:04 +08:00
dependabot[bot] 00f1773495 chore: bump golangci/golangci-lint-action from 4 to 5 (#3298)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 4 to 5.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-29 23:28:15 +08:00
Steven 8a3845ff54 chore: remove external resource dialog 2024-04-29 22:57:47 +08:00
Steven 155c5baf2c refactor: add markdown service 2024-04-29 08:00:37 +08:00
Steven 4338234641 chore: retire storage related functions 2024-04-28 21:50:09 +08:00
Steven 320963098f refactor: update storage setting 2024-04-28 21:36:22 +08:00
Steven f25c7d9b24 fix: serve frontend assets 2024-04-28 08:36:33 +08:00
Steven d8aeec993c chore: replace classnames with clsx 2024-04-28 00:58:40 +08:00
Steven 1b291422e7 refactor: api version 2024-04-28 00:44:29 +08:00
Steven 8bba7f706e chore: update IME mode checks 2024-04-28 00:12:44 +08:00
Steven f9942002f9 chore: fix linter 2024-04-27 23:36:57 +08:00
Steven 92872118b9 refactor: tweak api definition 2024-04-27 23:14:58 +08:00
Steven 9b66ef5e26 chore: tweak api definition 2024-04-27 22:02:15 +08:00
Steven 04c78e180c chore: update buf dependencies 2024-04-27 00:37:45 +08:00
Michael cc2370e01e chore: update i18n with Weblate (#3287)
* Translated using Weblate (Japanese)

Currently translated at 91.0% (276 of 303 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/ja/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.6% (302 of 303 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hans/

---------

Co-authored-by: mirukupc <mirukupc.jp@gmail.com>
Co-authored-by: Coisini-H <2972186196@qq.com>
2024-04-27 00:32:58 +08:00
Steven 7217605831 chore: update gitignore 2024-04-27 00:26:04 +08:00
Steven a6a62eb79c chore: update gitignore 2024-04-27 00:25:47 +08:00
Steven 5f26c52b49 feat: add goreleaser 2024-04-27 00:22:27 +08:00
Steven 647602beac chore: update link preview 2024-04-25 21:01:13 +08:00
Steven 81d24f32a7 fix: memo comment keys 2024-04-25 09:10:11 +08:00
SkyWT 9bcd4f59a2 fix: editor IME composing event behavior (#3267)
* fix: editor IME composing event behavior

* fix: editor IME composing event behavior

* Update web/src/components/MemoEditor/hooks/useAutoComplete.ts

---------

Co-authored-by: boojack <stevenlgtm@gmail.com>
2024-04-25 08:59:26 +08:00
Steven 205bf7ed0e chore: update link target 2024-04-24 22:02:11 +08:00
Steven cdcb61da17 chore: fix update storage 2024-04-24 09:18:33 +08:00
Michael 7f9af9b12c chore: translated using Weblate (French) (#3272)
Translated using Weblate (French)

Currently translated at 99.6% (302 of 303 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/fr/

Co-authored-by: Alexis <alexisl61@outlook.fr>
2024-04-24 06:22:57 +08:00
dependabot[bot] 6b253247cb chore: bump modernc.org/sqlite from 1.29.7 to 1.29.8 (#3266)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.29.7 to 1.29.8.
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.29.7...v1.29.8)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-23 10:04:10 +08:00
Michael 761b4e115b chore: translated using Weblate (English) (#3259)
Translated using Weblate (English)

Currently translated at 100.0% (303 of 303 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/en/

Co-authored-by: noxonad <noxonad@proton.me>
2024-04-22 10:11:30 +08:00
Nabiel Omar Syarif 16f209eada fix: specify go toolchain in go.mod (fix #3252) (#3253) 2024-04-22 10:06:33 +08:00
刘明野 06a79a2e37 fix: tag dependencies (#3251)
fix: 修改tag名称后重新请求数据
2024-04-20 21:16:17 +08:00
Michael 330cc3dc89 chore: translated using Weblate (French) (#3249)
Translated using Weblate (French)

Currently translated at 99.6% (302 of 303 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/fr/

Co-authored-by: Alexis <alexisl61@outlook.fr>
2024-04-19 08:58:39 +08:00
Steven e8dfd579c3 chore: update background services 2024-04-18 23:34:35 +08:00
Steven 2a93b8d720 chore: tweak linter 2024-04-18 21:44:46 +08:00
Steven 5d967f41d9 chore: update server 2024-04-18 21:41:00 +08:00
Steven 339fecbfff chore: allow search comments 2024-04-18 21:04:10 +08:00
Steven 2cdcd17ba3 chore: tweak linter 2024-04-17 09:00:16 +08:00
Steven 14d4cfd5a4 chore: tweak store methods name 2024-04-17 08:56:52 +08:00
Michael 00d25b12c1 chore: translated using Weblate (Russian) (#3245)
Translated using Weblate (Russian)

Currently translated at 90.4% (274 of 303 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/ru/

Co-authored-by: sergeybutakov <pushkagun@gmail.com>
2024-04-16 22:38:20 +08:00
Steven 95df647265 chore: tweak comments 2024-04-16 22:33:25 +08:00
Steven f52e0e005a chore: update dependencies 2024-04-16 21:28:40 +08:00
Steven 70f3d131f6 chore: update pnpm version 2024-04-16 21:22:40 +08:00
Steven f7f139d65a chore: tweak typo 2024-04-16 21:15:17 +08:00
coderwander 162521885c chore: fix some typos in comments (#3243)
Signed-off-by: coderwander <770732124@qq.com>
2024-04-16 21:14:38 +08:00
dependabot[bot] b0b9513de7 chore: bump @types/react from 18.2.75 to 18.2.79 in /web (#3240)
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 18.2.75 to 18.2.79.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-16 07:36:03 +08:00
dependabot[bot] f221f9bfe9 chore: bump i18next from 23.11.1 to 23.11.2 in /web (#3235)
Bumps [i18next](https://github.com/i18next/i18next) from 23.11.1 to 23.11.2.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v23.11.1...v23.11.2)

---
updated-dependencies:
- dependency-name: i18next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-16 07:08:40 +08:00
dependabot[bot] e831fa8190 chore: bump react-redux from 9.1.0 to 9.1.1 in /web (#3234)
Bumps [react-redux](https://github.com/reduxjs/react-redux) from 9.1.0 to 9.1.1.
- [Release notes](https://github.com/reduxjs/react-redux/releases)
- [Changelog](https://github.com/reduxjs/react-redux/blob/master/CHANGELOG.md)
- [Commits](https://github.com/reduxjs/react-redux/compare/v9.1.0...v9.1.1)

---
updated-dependencies:
- dependency-name: react-redux
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-16 07:08:23 +08:00
dependabot[bot] eae0b7115b chore: bump @types/react-dom from 18.2.24 to 18.2.25 in /web (#3233)
Bumps [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) from 18.2.24 to 18.2.25.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

---
updated-dependencies:
- dependency-name: "@types/react-dom"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-16 07:08:01 +08:00
dependabot[bot] 1f0fffda95 chore: bump lucide-react from 0.367.0 to 0.368.0 in /web (#3232)
Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 0.367.0 to 0.368.0.
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.368.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-16 07:07:49 +08:00
Michael 404751f378 chore: update i18n with Weblate (#3230)
* Translated using Weblate (Turkish)

Currently translated at 100.0% (303 of 303 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/tr/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.6% (302 of 303 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hans/

---------

Co-authored-by: Oğuz Han <h4n.3545@gmail.com>
Co-authored-by: Coisini-H <2972186196@qq.com>
2024-04-15 11:19:52 +08:00
Steven f9dd29ae07 chore: tweak tag store 2024-04-14 22:20:09 +08:00
Steven 1f0bfd2169 chore: update tag store 2024-04-14 22:03:56 +08:00
Michael 4aa72306ff chore: update i18n with Weblate (#3227)
* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (308 of 308 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/pt_BR/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.6% (307 of 308 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hans/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/

---------

Co-authored-by: Lincoln Nogueira <lincolnthalles@users.noreply.github.com>
Co-authored-by: Coisini-H <2972186196@qq.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
2024-04-14 21:27:55 +08:00
Eng Zer Jun 3b550a8ab8 chore: replace util.Min helper with built-in min (#3224) 2024-04-13 20:16:22 +08:00
Steven cb7886dc49 chore: retire telegram integration in frontend 2024-04-13 12:42:34 +08:00
Steven d00e4fdf17 chore: retire telegram plugin 2024-04-13 12:39:41 +08:00
Steven 9a2c423435 chore: update var-naming 2024-04-13 12:11:59 +08:00
Steven 49f8cfd5d1 chore: update jwt middleware 2024-04-13 12:10:57 +08:00
Steven b27004daae chore: retire unused plugin 2024-04-13 12:09:32 +08:00
Steven 75359854cc chore: fix resource routes 2024-04-13 12:07:53 +08:00
Steven cebc46adc7 chore: tweak store definition 2024-04-13 11:54:37 +08:00
Steven 6ee3b0f704 chore: go mod tidy 2024-04-13 11:02:29 +08:00
Steven bbd206e893 chore: retire legacy api 2024-04-13 11:01:16 +08:00
Steven cf4db17080 chore: fix linter 2024-04-13 10:53:39 +08:00
Steven c373131b89 chore: migrate idp service 2024-04-13 10:50:25 +08:00
Steven a77703260f chore: fix linter 2024-04-13 02:57:32 +08:00
Steven 8f51529c78 chore: implement storage service 2024-04-13 02:55:40 +08:00
Steven 707e5caf89 chore: update workspace setting store 2024-04-13 02:08:35 +08:00
Michael 17e8fc5408 chore: update i18n with Weblate (#3220)
* Translated using Weblate (Chinese (Simplified))

Currently translated at 98.0% (302 of 308 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 98.0% (302 of 308 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hans/

---------

Co-authored-by: Coisini-H <2972186196@qq.com>
Co-authored-by: LibreTranslate <noreply-mt-libretranslate@weblate.org>
2024-04-12 22:45:12 +08:00
Steven 074e7cf71a chore: fix resource path 2024-04-12 08:57:34 +08:00
Steven f5461264c5 chore: fix dialog background color 2024-04-12 08:44:19 +08:00
Michael e2ae32063e chore: translated using Weblate (Turkish) (#3215)
Translated using Weblate (Turkish)

Currently translated at 100.0% (300 of 300 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/tr/

Co-authored-by: Oğuz Han <h4n.3545@gmail.com>
2024-04-12 08:36:38 +08:00
Steven 133951328b chore: fix linter 2024-04-12 08:36:02 +08:00
imikod 257b8add8c chore: localization improvements (#3213)
* localization improvements

* typo fix

* fix linting error
2024-04-12 08:35:14 +08:00
Steven 755d5b83c6 chore: retire legacy setting api 2024-04-12 08:32:54 +08:00
Steven 3088cabe10 chore: tweak linter 2024-04-11 17:58:49 +08:00
Steven 3e6e56b008 refactor: update workspace store definition 2024-04-11 17:53:00 +08:00
Steven 6d842711e2 chore: fix dependencies 2024-04-10 23:34:29 +08:00
dependabot[bot] b84a0592bb chore: bump react-i18next from 11.18.6 to 14.1.0 in /web (#3210)
Bumps [react-i18next](https://github.com/i18next/react-i18next) from 11.18.6 to 14.1.0.
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v11.18.6...v14.1.0)

---
updated-dependencies:
- dependency-name: react-i18next
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-10 23:28:11 +08:00
dependabot[bot] 581a64d5c1 chore: bump lucide-react from 0.309.0 to 0.367.0 in /web (#3208)
Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 0.309.0 to 0.367.0.
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.367.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-10 23:27:34 +08:00
dependabot[bot] 4537411109 chore: bump @typescript-eslint/eslint-plugin from 6.21.0 to 7.0.0 in /web (#3207)
chore: bump @typescript-eslint/eslint-plugin in /web

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 6.21.0 to 7.0.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.0.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-10 23:26:53 +08:00
dependabot[bot] d6b1326a2d chore: bump eslint-config-prettier from 8.10.0 to 9.1.0 in /web (#3206)
Bumps [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) from 8.10.0 to 9.1.0.
- [Changelog](https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-config-prettier/compare/v8.10.0...v9.1.0)

---
updated-dependencies:
- dependency-name: eslint-config-prettier
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-10 23:26:36 +08:00
dependabot[bot] 9490e890c7 chore: bump i18next from 21.10.0 to 23.11.1 in /web (#3205)
Bumps [i18next](https://github.com/i18next/i18next) from 21.10.0 to 23.11.1.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v21.10.0...v23.11.1)

---
updated-dependencies:
- dependency-name: i18next
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-10 23:26:22 +08:00
dependabot[bot] aed76165ea chore: bump @reduxjs/toolkit from 1.9.7 to 2.2.3 in /web (#3204)
Bumps [@reduxjs/toolkit](https://github.com/reduxjs/redux-toolkit) from 1.9.7 to 2.2.3.
- [Release notes](https://github.com/reduxjs/redux-toolkit/releases)
- [Commits](https://github.com/reduxjs/redux-toolkit/compare/v1.9.7...v2.2.3)

---
updated-dependencies:
- dependency-name: "@reduxjs/toolkit"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-10 23:26:11 +08:00
dependabot[bot] 51a90b0013 chore: bump golangci/golangci-lint-action from 3 to 4 (#3203)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3 to 4.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-10 23:25:53 +08:00
dependabot[bot] 7b1fe9fcfc chore: bump pnpm/action-setup from 2.4.0 to 3.0.0 (#3202)
Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 2.4.0 to 3.0.0.
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/v2.4.0...v3.0.0)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-10 23:25:42 +08:00
Steven ec67f1b3c9 chore: add dependabot 2024-04-10 23:24:17 +08:00
Steven 97cfab1758 chore: upgrade dependencies 2024-04-10 23:19:59 +08:00
Steven eb251a097e chore: update workspace setting service 2024-04-10 23:01:01 +08:00
Steven 4c47e93fce chore: tweak workspace setting store 2024-04-10 22:31:55 +08:00
Michael af954db473 chore: update i18n with Weblate (#3201)
* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (299 of 299 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/pt_BR/

* Translated using Weblate (Turkish)

Currently translated at 100.0% (299 of 299 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/tr/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (299 of 299 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (299 of 299 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hans/

---------

Co-authored-by: Lincoln Nogueira <lincolnthalles@users.noreply.github.com>
Co-authored-by: Oğuz Han <h4n.3545@gmail.com>
Co-authored-by: Coisini-H <2972186196@qq.com>
Co-authored-by: Weblate Translation Memory <noreply-mt-weblate-translation-memory@weblate.org>
2024-04-10 22:05:31 +08:00
Steven a376dc4dd6 chore: fix linter 2024-04-10 22:04:02 +08:00
Dubzer 8ee56bd29f feat: add code wrapping option on mobile devices (#3196)
* feat: add code wrapping option on mobile devices

* Minor changes

* oopsie
2024-04-10 22:03:47 +08:00
Steven 71c39ed554 chore: update workspace setting definition 2024-04-10 21:15:55 +08:00
Steven c93b1efbae chore: update workspace setting store 2024-04-10 20:05:17 +08:00
Steven 58ae3217ff chore: update auth callback messages 2024-04-09 21:23:17 +08:00
Steven 6028838f03 chore: update component name 2024-04-09 20:51:09 +08:00
Michael 000b3a7a2c chore: update i18n with weblate (#3195)
Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/

Co-authored-by: Hosted Weblate <hosted@weblate.org>
2024-04-09 20:29:26 +08:00
Steven bbdd40b2b0 chore: add placeholder to comment editor 2024-04-09 20:01:19 +08:00
imikod 8356ebc46b chore: improve localization (#3190)
* improve localization

* follow suggestion
2024-04-09 16:11:32 +08:00
Steven fc95876617 chore: update memo creator display 2024-04-09 09:05:34 +08:00
Steven b1e196bb4f fix: delete memo reaction 2024-04-08 20:52:46 +08:00
Steven 6d10251cbd feat: impl list syntax auto complete to editor 2024-04-08 20:42:53 +08:00
Steven 436a6cb084 chore: fix text truncate 2024-04-08 19:20:16 +08:00
imikod 90ed908d2e fix: set navigation icons to not shrink (#3186) 2024-04-08 10:16:52 +08:00
Steven 756ab71302 chore: tweak truncate styles 2024-04-07 23:30:28 +08:00
Steven a6b09aa5b1 chore: add confirm dialog for disable password login setting 2024-04-07 23:22:59 +08:00
Steven ff81ea602d chore: tweak error message 2024-04-07 22:35:02 +08:00
Steven 8101a5e0b1 chore: add origin flag to config cors 2024-04-07 22:15:15 +08:00
Michael b5893aa60b chore: update i18n with Weblate (#3183)
* Translated using Weblate (French)

Currently translated at 100.0% (295 of 295 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/fr/

* Translated using Weblate (Turkish)

Currently translated at 100.0% (295 of 295 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/tr/

---------

Co-authored-by: Ezmana <ezmana.land@gmail.com>
Co-authored-by: Oğuz Han <h4n.3545@gmail.com>
2024-04-07 10:13:12 +08:00
Steven bedf3b3025 chore: tweak embedded memo styles 2024-04-02 23:32:43 +08:00
The Anh Nguyen 09ffa7b818 chore: fix memo link (#3173) 2024-04-02 07:13:58 +08:00
Steven ba7dbc1fca chore: fix task node click handler 2024-04-01 21:38:18 +08:00
Steven 15ef57589e chore: go mod tidy 2024-04-01 21:35:58 +08:00
stayweek 5aa633948e chore: use the built-in slices library (#3172)
Signed-off-by: stayweek <jiangtengtao@outlook.com>
2024-04-01 17:16:29 +08:00
Steven b1297f5d01 chore: tweak linter 2024-04-01 00:41:25 +08:00
Steven 33e9b13665 chore: tweak linter 2024-04-01 00:34:51 +08:00
Steven b79f626a74 chore: fix memo comment inbox 2024-04-01 00:26:46 +08:00
Michael ec35a42fb5 chore: update i18n with Weblate (#3168)
* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (295 of 295 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/pt_BR/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (295 of 295 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 98.3% (290 of 295 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hant/

---------

Co-authored-by: Lincoln Nogueira <lincolnthalles@users.noreply.github.com>
Co-authored-by: Coisini-H <2972186196@qq.com>
2024-04-01 00:07:03 +08:00
Michael 98c9ab70e2 chore: update translation files (#3166)
Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/

Co-authored-by: Hosted Weblate <hosted@weblate.org>
2024-03-30 22:54:38 +08:00
Steven edc7645086 chore: fix linter 2024-03-30 15:01:19 +08:00
Steven cbebbca7d6 chore: add idp definition 2024-03-30 14:58:47 +08:00
Steven 4d62ed46cc chore: tweak words 2024-03-30 14:14:42 +08:00
Steven bb10bb200c chore: implement search random memos 2024-03-30 13:50:18 +08:00
Steven 03c93785f4 chore: implement list random users 2024-03-30 13:29:48 +08:00
The Anh Nguyen 2fe6d606ec feat: handle restore memo when is in archived memo detail page (#3165) 2024-03-30 13:06:08 +08:00
Steven c8baeb86ec chore: fix memo links 2024-03-30 13:04:40 +08:00
The Anh Nguyen 9a88e00df0 fix: link embedded content to uid instead of name (#3164) 2024-03-30 12:30:09 +08:00
Steven 54a3c25ebd chore: update memo resource name definition 2024-03-30 08:26:28 +08:00
Steven d84a88e805 chore: fix linter 2024-03-30 00:05:19 +08:00
Steven 5ab845d92e chore: fix linter 2024-03-30 00:02:34 +08:00
Steven 4f7a6cd6cc chore: fix default logo url 2024-03-29 23:12:30 +08:00
Steven c7aaf791e6 chore: tweak props naming 2024-03-29 22:49:19 +08:00
Michael 239348c403 chore: update i18n with Weblate (#3162)
* Translated using Weblate (Russian)

Currently translated at 85.1% (252 of 296 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/ru/

* Translated using Weblate (Vietnamese)

Currently translated at 91.8% (272 of 296 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/vi/

---------

Co-authored-by: LibreTranslate <noreply-mt-libretranslate@weblate.org>
Co-authored-by: The Anh Nguyen <anhntdev0@gmail.com>
2024-03-29 21:32:55 +08:00
Michael 9f803aa9bb chore: translated using Weblate (Russian) (#3161)
Translated using Weblate (Russian)

Currently translated at 85.1% (252 of 296 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/ru/

Co-authored-by: xyznetsov <invisiblesun2016@yandex.ru>
2024-03-29 21:29:49 +08:00
Steven 200a0d3e2b fix: memo relations 2024-03-29 21:27:38 +08:00
Steven ea6628066d chore: update sidebar components 2024-03-29 09:16:55 +08:00
Steven 526f46807e chore: tweak user list display 2024-03-29 08:37:07 +08:00
Michael bfaf06582c chore: translated using Weblate (Portuguese (Brazil)) (#3157)
Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (296 of 296 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/pt_BR/

Co-authored-by: Lincoln Nogueira <lincolnthalles@users.noreply.github.com>
2024-03-29 08:20:56 +08:00
Steven 7331c598df fix: edit account dialog fields 2024-03-29 08:19:32 +08:00
The Anh Nguyen f7a445ac8b chore: update Vietnamese translation (#3155) 2024-03-29 08:07:02 +08:00
Steven 90679cc33a chore: add explore sidebar 2024-03-29 00:01:45 +08:00
Steven 192ee7acc0 chore: fix memo link 2024-03-28 22:53:22 +08:00
Michael 7d94256a2a chore: update i18n with Weblate (#3152)
* Translated using Weblate (Chinese (Traditional))

Currently translated at 98.3% (291 of 296 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hant/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 98.3% (291 of 296 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hant/

---------

Co-authored-by: Weblate Translation Memory <noreply-mt-weblate-translation-memory@weblate.org>
Co-authored-by: Coisini-H <2972186196@qq.com>
2024-03-28 22:51:50 +08:00
Michael aed4c1392d chore: update i18n with Weblate (#3151)
* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (296 of 296 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 98.3% (291 of 296 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hant/

---------

Co-authored-by: Coisini-H <2972186196@qq.com>
2024-03-28 22:48:32 +08:00
Paul 3605efc4d1 chore: removed sanitization logic (#3147)
* removed sanitization logic

* to avoid lint err
2024-03-28 22:44:44 +08:00
Dubzer a0846c2818 feat: add shortcut to edit the previous memo (#3122)
* Add shortcut to edit the previous memo

* Fix compilation

* Update web/src/components/MemoEditor/index.tsx

Co-authored-by: boojack <stevenlgtm@gmail.com>

---------

Co-authored-by: boojack <stevenlgtm@gmail.com>
2024-03-27 21:46:29 +08:00
Steven 2ebea4dba9 chore: fix workspace profile api 2024-03-27 18:51:11 +08:00
Steven 16cfef32d6 chore: tweak statistics translation 2024-03-26 23:49:37 +08:00
Steven f637deacfc chore: tweak tag list 2024-03-26 23:43:31 +08:00
Steven a07805907b chore: fix action run 2024-03-26 22:46:17 +08:00
Steven 2e87da4927 chore: update frontend dependencies 2024-03-26 22:35:11 +08:00
Steven d460e6bf41 fix: link node 2024-03-25 23:59:19 +08:00
Steven e703b4f70d fix: resource v1 definition 2024-03-24 22:35:10 +08:00
Steven be525fa3df chore: fix migration script 2024-03-23 01:03:11 +08:00
Steven d1e8af48c0 chore: tweak memo view styles 2024-03-23 00:58:15 +08:00
Steven 005305cd9a chore: update relative time display 2024-03-22 08:12:08 +08:00
Steven 8fd1dff484 chore: update migration scripts 2024-03-21 22:24:39 +08:00
Steven c5fa4fe304 chore: tweak imports order 2024-03-21 21:44:43 +08:00
Steven 8f37d7490c chore: fix linter 2024-03-21 21:42:44 +08:00
Steven 18d16abdb5 chore: update workspace service 2024-03-21 21:39:34 +08:00
Steven 1d83c68cb5 chore: update type definitions 2024-03-21 08:12:38 +08:00
Michael 861a1e4c9b chore: update i18n with Weblate (#3115)
* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (293 of 293 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (293 of 293 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hans/

---------

Co-authored-by: Coisini-H <2972186196@qq.com>
Co-authored-by: Weblate Translation Memory <noreply-mt-weblate-translation-memory@weblate.org>
2024-03-20 21:42:08 +08:00
Steven 26e5b70483 chore: upgrade frontend dependencies 2024-03-20 21:32:06 +08:00
Steven 7c5261b5d2 chore: tweak resource definition 2024-03-20 21:17:04 +08:00
Steven 7cc8b951a3 refactor: update resource id naming 2024-03-20 20:39:16 +08:00
Steven a3a4e37cb0 chore: update memo content styles 2024-03-20 09:07:54 +08:00
Steven 76c936357b chore: fix resource link 2024-03-20 08:51:32 +08:00
Steven 6db427e396 chore: update acl list 2024-03-20 08:18:43 +08:00
Steven 0a0ccbf6e1 chore: update acl list 2024-03-20 08:12:46 +08:00
Steven 96a7d0eb25 chore: tweak line height 2024-03-20 00:40:46 +08:00
Steven ed89cb8310 chore: update memo relation definition 2024-03-19 21:53:44 +08:00
Steven 722e356044 chore: update memo store 2024-03-19 20:55:33 +08:00
Steven efb15a0453 chore: tweak naming 2024-03-18 23:41:57 +08:00
Steven 5f2d6b22be chore: tweak memo structure 2024-03-18 23:23:53 +08:00
Steven 2dc8ed773c chore: tweak linter 2024-03-18 19:57:53 +08:00
Steven 86db6d0254 chore: tweak user api definition 2024-03-18 12:56:52 +08:00
Steven f3fb5e0c60 chore: tweak route enum 2024-03-18 12:07:44 +08:00
Steven 1e43c8d84b chore: disallow update workspace setting in demo mode 2024-03-17 19:18:45 +08:00
Steven cf207df672 chore: tweak link styles 2024-03-17 18:28:37 +08:00
Michael 5dd1251d1e chore: update i18n with Weblate (#3109)
* Translated using Weblate (Japanese)

Currently translated at 95.2% (279 of 293 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/ja/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (293 of 293 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/pt_BR/

---------

Co-authored-by: Somme4096 <somme4096@gmail.com>
Co-authored-by: Lincoln Nogueira <lincolnthalles@users.noreply.github.com>
2024-03-17 18:22:22 +08:00
sunxunle ea104a5e54 chore: fix typo (#3106) 2024-03-17 09:52:04 +08:00
Steven 335a0312f2 chore: tweak link styles 2024-03-16 23:35:36 +08:00
Mehad Nadeem 9c1e2f8137 feat: implemented link previews (frontend files) (#3074)
* feat: implmented link previews (frontend files)

* chore: updated frontend side for Link Previews

* chore: updated frontend gen types with the renamed (server) service file

* fix: passing errors

* chore: switched to using generated type instead of separate fields

* fix: passing linter error

* chore: updated Link.tsx

* chore: using `useResponsiveWidth` to render for different devices

* chore: refactored Link.tsx
2024-03-16 22:51:16 +08:00
Steven 14479347d8 chore: remove demo checks 2024-03-15 09:05:02 +08:00
Steven 0f48cfbb4e chore: tweak padding styles 2024-03-15 08:52:11 +08:00
Steven 606a30640d fix: check username in signup 2024-03-15 08:37:58 +08:00
Steven ab136e3310 chore: tweak styles 2024-03-15 00:12:22 +08:00
Michael 21af2a004d chore: translated using Weblate (French) (#3099)
Translated using Weblate (French)

Currently translated at 100.0% (293 of 293 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/fr/

Co-authored-by: Ezmana <ezmana.land@gmail.com>
2024-03-14 17:09:29 +08:00
Steven 115b5551b3 chore: tweak default avatar 2024-03-14 09:11:57 +08:00
Steven 15be18fa85 chore: update compace mode handler 2024-03-14 09:07:18 +08:00
Steven 9ce381abb9 chore: tweak user profile 2024-03-14 00:13:56 +08:00
Steven 2516431b20 chore: tweak memo content styles 2024-03-14 00:01:33 +08:00
Steven ded4da07a3 feat: use @github/relative-time-element to display time 2024-03-13 23:47:34 +08:00
Steven e795149186 chore: tweak memo view display 2024-03-13 22:30:59 +08:00
Steven 8fe6874b1b chore: add description field to user 2024-03-13 21:24:16 +08:00
boojack 7c1510e7a9 chore: update i18n with Weblate (#3090)
* Translated using Weblate (Spanish)

Currently translated at 95.4% (293 of 307 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/es/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (307 of 307 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hans/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/

---------

Co-authored-by: Hisiste <aditooliva@gmail.com>
Co-authored-by: keocheung <keocheung@outlook.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
2024-03-13 20:55:04 +08:00
Steven e5fc107920 chore: tweak i18n locales 2024-03-13 20:51:19 +08:00
Steven 5d740c3813 chore: update link service 2024-03-13 20:25:27 +08:00
Mehad Nadeem 0abe20df72 feat: implemented link previews (server files) (#3073)
* feat: implmented link previews (server files)

* chore: updated variable name

* chore: renamed service file from `metadata_service.go` to `link_service.go`

* fix: passing errors

* fix: fixed linter warnong about `ctx`
2024-03-13 17:31:53 +08:00
Mehad Nadeem 4d41b68d4c feat: implemented link previews (proto files) (#3072)
* feat: implmented link previews (proto files)

* fix: passing proto linter errors

* chore: renamed MetadataService service in proto file.

* fix: removed unused files

* chore: removed meaningless comments from proto file
2024-03-13 16:52:59 +08:00
Lincoln Nogueira 7c87c1ff74 chore: update build scripts (#3085) 2024-03-13 08:50:56 +08:00
Steven 963c630428 chore: update readme with weblate 2024-03-13 08:25:38 +08:00
Steven 6b6edc3791 chore: update tg message handler 2024-03-12 22:48:53 +08:00
Mario Gómez 425e85f0f9 chore: fix spanish typos (#3083) 2024-03-12 21:48:02 +08:00
huajin tong 8e7f826ae6 chore: fix typo (#3080)
Signed-off-by: thirdkeyword <fliterdashen@gmail.com>
2024-03-12 17:05:49 +08:00
Michael 9ffc1515f4 chore: update i18n with Weblate (#3079)
* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (307 of 307 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (307 of 307 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hans/

---------

Co-authored-by: Matsuri <matsuri@vmoe.info>
Co-authored-by: keocheung <keocheung@outlook.com>
2024-03-12 13:14:56 +08:00
steven 8cdc0c7ffa chore: add delete tag action 2024-03-09 17:26:58 +08:00
zty 999a05307e fix: body overflow styles #3056
Co-authored-by: zty <zty.dev@outlook.com>
2024-03-09 14:09:31 +08:00
steven ed1954c58c chore: fix check setting value 2024-03-09 12:56:44 +08:00
steven 77bafba682 chore: update gomark.wasm 2024-03-09 12:47:20 +08:00
Michael 1540de2f44 chore: translated using Weblate (Korean) (#3060)
Translated using Weblate (Korean)

Currently translated at 95.1% (292 of 307 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/ko/

Co-authored-by: WonSeok <wonseok@duck.com>
2024-03-09 12:40:42 +08:00
Steven ed4b48c54f chore: update gomark.wasm 2024-03-08 00:34:34 +08:00
Michael 706e749275 chore: translated using Weblate (Portuguese (Brazil)) (#3050)
Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (307 of 307 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/pt_BR/

Co-authored-by: Lincoln Nogueira <lincolnthalles@users.noreply.github.com>
2024-03-07 11:38:24 +08:00
Steven 845999292a chore: update frontend dependencies 2024-03-06 23:33:00 +08:00
Michael 240da335c1 chore(i18n): update translation files (#3042)
Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/

Co-authored-by: Hosted Weblate <hosted@weblate.org>
2024-03-05 11:44:51 +08:00
Steven f9f277695d chore: fix comment amount 2024-03-04 23:37:38 +08:00
Dubzer 6cbfbe9c07 chore: settings page improvements (#3034)
* Don't allow to select text on buttons in settings + refactoring

* Remove redundant useMemo

* Update web/src/pages/Setting.tsx

Co-authored-by: boojack <stevenlgtm@gmail.com>

* Rename other constants too

* Rename AdminSection to ADMIN_SECTIONS

---------

Co-authored-by: boojack <stevenlgtm@gmail.com>
2024-03-04 23:33:43 +08:00
Steven ec206104e5 chore: tweak i18n locale 2024-03-04 23:23:14 +08:00
Steven 342f341b3d chore: ignore import locale error 2024-03-04 13:16:55 +08:00
WonSeok f46b64a17d fix: check disallow public memo in Telegram(#3036) (#3037) 2024-03-04 11:24:09 +08:00
Steven 7d5f603482 chore: update compact view 2024-03-04 11:21:48 +08:00
Dubzer f0a521f5b8 feat: improve theming (#3032)
Add color-scheme and dark scrollbar styles
2024-03-04 09:35:40 +08:00
Steven 59314cdf80 chore: add compact mode to memo view 2024-03-04 01:11:34 +08:00
Steven ac0315334d chore: update i18n locales imports 2024-03-04 00:05:27 +08:00
Steven ac8a374ebd chore: bump version 2024-03-03 23:45:52 +08:00
Steven 95b02341eb chore: tweak logger 2024-03-03 23:45:32 +08:00
Steven d34273b186 chore: update memo icons style 2024-03-03 16:41:03 +08:00
Steven dcfb2b7de2 chore: tweak docs generate path 2024-03-03 15:05:42 +08:00
Steven ebcf43c997 chore: update gomark.wasm 2024-03-03 14:56:40 +08:00
Steven cfb50f19aa chore: tweak linter 2024-03-03 14:23:21 +08:00
Steven e441e3882b chore: update error logger 2024-03-03 14:20:59 +08:00
Steven c1da87a819 chore: update get request origin 2024-03-03 14:10:48 +08:00
jjaychen 3b089eeae3 fix: ignore "Tab" key down event when is composing in editor(#3026) (#3027) 2024-03-03 13:04:00 +08:00
Steven 9a8a1d017e chore: add comments icon 2024-03-02 11:29:21 +08:00
Steven 7e23ceb242 chore: fix frontend linter 2024-03-01 19:39:24 +08:00
Steven 898b3c3779 chore: update gomark.wasm 2024-03-01 19:31:00 +08:00
Steven 8eb01adb6f chore: go mod tidy 2024-02-29 23:56:20 +08:00
jg0312 621a8d4e20 chore: fix typo in build script's if statement (#3017)
missing 'i' in 'if' statement
2024-02-29 23:55:25 +08:00
Steven dfa78cac49 chore: update logger 2024-02-29 23:54:43 +08:00
Steven 12f4d3a10d chore: tweak default page size 2024-02-29 23:42:06 +08:00
Steven 00788bd8f8 chore: update actions 2024-02-29 09:21:55 +08:00
Steven 5f2a624c0c chore: tweak imports order 2024-02-29 01:32:59 +08:00
Steven 3e50bee7da chore: move api directory 2024-02-29 01:16:43 +08:00
Steven 1aa75847d6 chore: upgrade gomark 2024-02-29 01:07:53 +08:00
Steven 89a270cede chore: add username to cache key 2024-02-28 00:34:55 +08:00
Xylit 5526355621 fix: dark mode tag selection and suggestion (#3004)
* fix: use correct higlight color on selecting a tag in darkmode

* fix: take scrolling into account in tab suggestion

Issue: When editing a long memo and editing a tag somewhere where the user has
to scroll down to, the tag suggestions would be out of place (too far down)
because the scrolling wouldn't be taken into account.

Fix: Substract the suggestions div by the scroll amount.

* fix: don't show tag suggestion when on '#'

Fixes this issue like this:
1. Text #tag text
        ^ backspace here
2. Text#tag text
       ^ space
3. Text #tag text
        ^ tag suggestion opens
2024-02-27 09:16:56 +08:00
ercd 1ae9bf23a0 fix: telegram callback query handler dereferencing nil pointer if memo not found (#3003)
* fix: telegram callback query handler dereferencing nil pointer if memo not found

* chore: add an answer to callback query if memo not found
2024-02-27 00:03:08 +08:00
Zhiyuan Zheng 51a7934616 fix: firefox/safari to redirect to /explore in HomeLayout (#3001) 2024-02-26 13:38:24 +08:00
Mr. Lin 8d08cfe1c9 fix: incorrectly checking whether registration is allowed in the login v1 api (#2998)
fix: Incorrectly checking whether registration is allowed in the login interface, resulting in the inability to log in through apiv1
2024-02-26 13:36:17 +08:00
Steven b0f52ade7a chore: remove metric service 2024-02-24 23:32:39 +08:00
Steven 222d04fb22 chore: add fuse to get tag suggestions 2024-02-24 11:02:46 +08:00
Steven 68468927dd chore: tweak setting styles 2024-02-24 11:01:57 +08:00
Steven dfe29ec766 chore: tweak route layout 2024-02-24 10:18:53 +08:00
Steven db56e1b575 chore: fix user banner dropdown 2024-02-23 23:47:26 +08:00
Steven 5b92ac1775 chore: fix setting migrator 2024-02-23 09:16:34 +08:00
Steven f2eb9f1b8f chore: fix get workspace setting 2024-02-23 09:11:39 +08:00
Steven e602aeecc1 fix: update workspace general setting 2024-02-23 09:08:49 +08:00
Steven ce133ad69b chore: retire unused workspace settings 2024-02-23 08:40:33 +08:00
Steven e585578553 chore: tweak readme 2024-02-23 08:25:54 +08:00
Steven 4d9c929c32 chore: upgrade gomark 2024-02-23 08:22:39 +08:00
Steven 39bf850591 fix: embed memos callback 2024-02-22 23:17:15 +08:00
Steven 9cd835b979 chore: upgrade gomark wasm 2024-02-22 23:11:32 +08:00
Steven 0afdbe3332 chore: remove animation of spoiler 2024-02-22 19:25:24 +08:00
Steven 6b14d87521 chore: fix linter 2024-02-21 23:45:12 +08:00
Steven 51d58d3982 chore: add workspace setting migrator 2024-02-21 23:43:18 +08:00
Steven 4378816e44 chore: tweak i18n 2024-02-21 23:02:18 +08:00
Steven e7bbd850b2 chore: tweak spoiler animation 2024-02-21 21:31:34 +08:00
Steven c6162d3f38 chore: update dependencies 2024-02-21 21:24:31 +08:00
Steven ce32206677 chore: remove unused system setting 2024-02-21 21:15:28 +08:00
Steven 50a3af3b29 chore: fix get general setting 2024-02-21 20:44:35 +08:00
Steven 80b64c02fd chore: tweak workspace setting seeds 2024-02-21 20:25:25 +08:00
Steven 13b911ebf0 chore: add spoiler node 2024-02-20 23:55:54 +08:00
Steven 4a6da91719 chore: fix serve frontend flag 2024-02-20 23:24:13 +08:00
Steven fa62e8b59a chore: tweak linter errors 2024-02-20 23:07:42 +08:00
Steven 8e11826db1 chore: update workspace setting service 2024-02-20 23:02:01 +08:00
David Nguyen e6d0c00cf6 chore: update vi.json (#2980) 2024-02-20 17:57:06 +08:00
Kristián 03d67d5a00 feat: add mermaid support in codeblock (#2971) 2024-02-19 15:10:58 +08:00
Kazuki H a86117f613 feat: add new line if the cursor is on a character when adding a tag (#2960) 2024-02-19 08:54:47 +08:00
Søm fc1a2cf2fc chore: update ja.json (#2966) 2024-02-17 09:31:52 +08:00
Steven d22b772232 chore: add memo actions to memo detail page 2024-02-15 11:16:51 +08:00
Steven f1ec5775a7 chore: update inbox props 2024-02-14 09:44:35 +08:00
Bryan 4aa4417d91 chore: allow all 20x response status code in webhook (#2947) 2024-02-13 09:30:48 +08:00
Steven 606e574e19 chore: update enum type 2024-02-13 09:30:28 +08:00
Brilliant Hanabi ebe3678288 feat: add visibility select in ShareMemoDialog (#2941)
In ShareMemoDialog, user can change the visibility
of the memo, so that the memo can be set to public
to be viewed by anyone with the link.
2024-02-13 09:28:16 +08:00
Steven b3ca9969c4 chore: tweak linter 2024-02-09 22:18:55 +08:00
Steven 3dddd3ec4c chore: tweak reaction store 2024-02-09 21:59:45 +08:00
Brilliant Hanabi 81aa9b107f feat: add notice when sharing private links in MemoDetail (#2942) 2024-02-09 09:30:01 +08:00
Steven 60efd3ac32 chore: tweak memo view 2024-02-08 22:52:49 +08:00
Steven 4081a6f5ad chore: add more reactions 2024-02-08 21:20:51 +08:00
Lincoln Nogueira 334e489867 chore: improve docker-compose.dev (#2938) 2024-02-08 16:28:43 +08:00
Steven c7822515a1 chore: tweak view checks 2024-02-08 13:37:38 +08:00
Steven d86f0bac8c chore: implement reaction frontend 2024-02-08 13:25:15 +08:00
Steven e5f244cb50 chore: fix tests 2024-02-08 11:58:23 +08:00
Steven 3a5bc82d39 chore: implement reaction service 2024-02-08 11:54:59 +08:00
Steven a4fa67cd18 chore: update dependencies 2024-02-08 08:13:42 +08:00
Steven 43a2d6ce09 chore: tweak user setting 2024-02-08 08:06:55 +08:00
Mehad Nadeem d2434111b4 chore: impl compact mode setting (#2935)
* chore: backend/DB related files for compact view setting.

* fix: passing lint errors

* fix2: passing linter errors
2024-02-08 08:05:56 +08:00
Steven 559e427c50 chore: implement reaction store 2024-02-07 23:40:23 +08:00
Steven 99568236a3 chore: run buf generate 2024-02-07 21:51:30 +08:00
Mehad Nadeem 06fb2174c3 feat: add compact mode setting (Proto) (#2934)
* chore: proto related files for compact view setting.

* fix: pasing lint errors
2024-02-07 21:45:11 +08:00
Kazuki H 5ac17fc012 fix: displaying archived memos (#2933)
* fix: web: Archived: Show displayTime instead of updateTime

Archiving a memo is considered "updating" it, so the time it was
archived will be displayed, instead of the time it was created.

* fix: web: Archived: Add an option to fetch more memos

Just like on other pages, add a button to fetch more memos. Otherwise,
the user would only be able to load the latest 10 memos, as defined in
DEFAULT_MEMO_LIMIT.
2024-02-07 17:53:23 +08:00
Steven a76b86f18a chore: fix highlight code 2024-02-06 20:59:17 +08:00
Steven ded8001735 chore: fix v2 routes 2024-02-06 20:55:27 +08:00
Steven 185ec2ad2a chore: update inbox service 2024-02-06 19:46:25 +08:00
Steven 6b59c7670c chore: fix linter warning 2024-02-05 23:32:01 +08:00
Steven 434ef44f8c chore: add cookie builder 2024-02-05 23:28:29 +08:00
Steven 46ea16ef7e chore: fix cookie attrs 2024-02-05 22:14:58 +08:00
Steven 8f15e8773a chore: update cookie attrs 2024-02-05 21:42:23 +08:00
Steven 25efc33b24 chore: tweak timeline styles 2024-02-05 21:37:43 +08:00
Steven ba460382b0 chore: remove type-gen script 2024-02-05 20:42:01 +08:00
Steven e35225ff24 chore: fix resource url 2024-02-05 19:35:23 +08:00
Steven 397a7f00ef chore: add postinstall script 2024-02-05 19:29:47 +08:00
Steven 06eff151e7 chore: tweak memo find builder 2024-02-05 06:40:55 +08:00
Steven c30d7ab8f3 chore: update cors middleware 2024-02-05 06:10:10 +08:00
Steven ab4a670bec chore: add env example 2024-02-05 00:33:07 +08:00
Steven ce663efc14 chore: update cookie attrs 2024-02-05 00:11:36 +08:00
Steven 9e72432f19 chore: tweak cookie attrs 2024-02-05 00:10:54 +08:00
Steven b056c59dea chore: add vercel.json 2024-02-05 00:01:31 +08:00
Steven 15c90871d9 chore: update request base url 2024-02-04 23:48:26 +08:00
Steven be899cd027 chore: update eslint config 2024-02-04 22:52:47 +08:00
Steven 8773a3d2c1 chore: tweak assets folder 2024-02-04 22:45:51 +08:00
Steven d2603ee67b chore: upgrade frontend dependencies 2024-02-04 21:36:11 +08:00
Steven c92507728a chore: tweak filter checks 2024-02-04 21:07:14 +08:00
Steven eb4f7b47b7 chore: update memo find builder 2024-02-04 20:54:17 +08:00
Steven 1e07b70d23 chore: fix export memos 2024-02-04 20:20:14 +08:00
Athurg Gooth b8a9783db5 fix: signin error notification is not shown (#2908)
fix signin error notification is not shown
2024-02-04 14:25:51 +08:00
Ikko Eltociear Ashimine 82e72813f9 chore: fix typo in About.tsx (#2899) 2024-02-04 08:33:13 +08:00
Steven 57510ddee5 chore: update readme with used resources 2024-02-03 23:42:24 +08:00
Steven 00c47a0673 chore: fix menu z-index 2024-02-03 23:17:03 +08:00
Steven 374f3f7d96 chore: fix filter initial state 2024-02-03 22:03:56 +08:00
Steven 8340e6b247 chore: upgrade gomark 2024-02-03 21:52:54 +08:00
Steven 7f5148d490 chore: clean dropdown 2024-02-01 21:56:08 +08:00
Steven c522e1450a chore: update codeblock style 2024-02-01 21:38:28 +08:00
Steven c342c464a2 chore: update services comment 2024-02-01 21:26:09 +08:00
Steven f6f193af2d chore: format proto 2024-02-01 21:15:56 +08:00
Steven dd06278692 feat: add batch upsert tags 2024-02-01 21:13:42 +08:00
Steven fdd17ce849 chore: update memo content key 2024-02-01 20:40:43 +08:00
Elliot Chen 7cd3fcbc61 fix: wrong order of the timeline in the resource page & add webhook when create memos using Telegram bot (#2886)
* fix: wrong order in resource page timeline

* feat: add webhook when create memos using Telegram bot

* rename variables and fix typos for static checks
2024-02-01 20:24:58 +08:00
Steven e78311b3af fix: initial gomark wasm 2024-02-01 19:39:41 +08:00
Steven e3afad74ce fix: update initial wasm 2024-02-01 19:27:54 +08:00
Steven 554f93eccc fix: move initial wasm into app 2024-02-01 19:06:44 +08:00
Steven 79227021f5 chore: bump version 2024-01-31 23:08:33 +08:00
Steven b4f2a3bd14 chore: remove migrator 2024-01-31 22:58:43 +08:00
Steven 0b4914d880 chore: update generated node types 2024-01-31 22:42:51 +08:00
Steven 2f0b0e0071 chore: remove node definition 2024-01-31 22:32:09 +08:00
Steven 8ce6a32aac feat: use gomark wasm in frontend 2024-01-31 22:25:24 +08:00
Wen Sun 3158c4b8b5 fix: role error in api/v2 when the first user registers (#2875)
Fix role error in api/v2 when the first user registers
2024-01-31 19:55:52 +08:00
Steven 30ae4140f3 chore: update gomark source 2024-01-31 19:01:08 +08:00
Lincoln Nogueira 279cba0e6b chore: greatly speed up migrator and lower memory usage (#2874)
* chore: add en-GB language

* chore: remove en-GB contents

* chore: prevent visitors from breaking demo
- prevent disabling password login
- prevent updating `memos-demo` user
- prevent setting additional style
- prevent setting additional script
- add some error feedback to system settings UI

* Revert "chore: add en-GB language"

This reverts commit 2716377b04.

* chore: speed-up migrator and lower memory usage
- remove all Store indirections
- query database directly with prepared statements

* chore: fix golangci-lint warnings
2024-01-31 16:45:21 +08:00
Lincoln Nogueira 52539fc130 chore: prevent visitors from breaking demo (#2869)
* chore: add en-GB language

* chore: remove en-GB contents

* chore: prevent visitors from breaking demo
- prevent disabling password login
- prevent updating `memos-demo` user
- prevent setting additional style
- prevent setting additional script
- add some error feedback to system settings UI

* Revert "chore: add en-GB language"

This reverts commit 2716377b04.
2024-01-31 13:16:31 +08:00
Steven 49e3eb107c chore: update gomark wasm 2024-01-31 00:25:01 +08:00
Lincoln Nogueira e7d5dfe515 chore: add en-GB language (#2865)
* chore: add en-GB language

* chore: remove en-GB contents
2024-01-30 23:39:13 +08:00
Steven 28c7a75ea2 chore: fix import nodes 2024-01-30 22:15:05 +08:00
Steven 59d69a05fa feat: initial gomark wasm importer 2024-01-30 22:12:44 +08:00
Steven ad2d492dec chore: move preview memo content 2024-01-30 22:10:17 +08:00
Elliot Chen bee6f278ba fix: the same-storage check in the new pre-sign feature (#2860)
* fix: error check for the same oss-storage

* fix: conflict error2 variable in code refactor in s3.go

* chore: rename endpointUrl to endpointURL
2024-01-30 19:07:16 +08:00
Brilliant Hanabi 1bad0543d0 feat: add notice when sharing private links (#2809)
Co-authored-by: boojack <stevenlgtm@gmail.com>
2024-01-30 19:06:30 +08:00
Noah Alderton 73337331cb feat: export all user Memos as a .zip of Markdown files (#2854)
* Add gRPC Memos Export

* Update code style

* Add URL.revokeObjectURL

* Rename protobuf and ESLint fix

* Change MemosExport to ExportMemos
2024-01-30 16:42:54 +08:00
Wen Sun 50f7f131ea fix: month grouping error in timeline page (#2861) 2024-01-30 07:56:03 +08:00
Steven a16bde23f7 chore: tweak variable name 2024-01-29 23:15:47 +08:00
Steven c5a5f67fdb refactor: migrate auth service 2024-01-29 23:12:02 +08:00
Steven de8db63811 chore: rename workspace setting service 2024-01-29 22:43:40 +08:00
Steven dd9ee44a1f docs: regenerate swagger 2024-01-29 22:05:33 +08:00
Aleksandr Baryshnikov fa17dce046 feat: pre-signed URL for S3 storage (#2855)
Adds automatically background refresh of all external links if they are belongs to the current blob (S3) storage. The feature is disabled by default in order to keep backward compatibility.

The background go-routine spawns once during startup and periodically signs and updates external links if that links belongs to current S3 storage.

The original idea was to sign external links on-demand, however, with current architecture it will require duplicated code in plenty of places. If do it, the changes will be quite invasive and in the end pointless: I believe, the architecture will be eventually updated to give more scalable way for pluggable storage. For example - Upload/Download interface without hard dependency on external link. There are stubs already, but I don't feel confident enough to change significant part of the application architecture.
2024-01-29 21:12:29 +08:00
Steven cbcec80c5d chore: fix import order 2024-01-29 21:08:30 +08:00
Steven 2b7bd47b44 fix: rss routes 2024-01-29 21:04:35 +08:00
Steven 54c5039db3 chore: fix golang linter 2024-01-29 19:17:25 +08:00
Steven af646ce2de refactor: move gomark 2024-01-29 19:14:46 +08:00
Steven f4ac7ff529 chore: update memo resource url 2024-01-28 23:02:38 +08:00
Steven 55ecdae509 chore: fix auto link matcher 2024-01-28 22:13:19 +08:00
Steven ef73299340 chore: update resource name migrator 2024-01-28 21:40:24 +08:00
Steven 8c6292925e chore: update code block styles 2024-01-28 15:41:11 +08:00
Steven f05a89315c chore: fix list memos 2024-01-28 08:38:29 +08:00
Steven a4452d8a2f chore: update linter rules 2024-01-28 08:17:11 +08:00
Steven 5e74394643 chore: add resource name migrator 2024-01-28 07:58:53 +08:00
Steven f4e722c516 chore: remove latest tag 2024-01-28 07:46:08 +08:00
Steven 12275c6a34 chore: fix linter warning 2024-01-28 07:38:01 +08:00
Steven 21ef5a9bc0 chore: tweak workspace service 2024-01-28 07:35:42 +08:00
Steven 87b23940a6 chore: upgrade backend dependencies 2024-01-28 07:13:11 +08:00
Steven 11dd23f59b chore: tweak link checks 2024-01-28 07:04:35 +08:00
Lincoln Nogueira 887903b66b feat: add buf plugin to generate openapiv2 spec (#2843) 2024-01-28 06:16:37 +08:00
Steven 309fab222e chore: implement nested blockquote 2024-01-27 21:38:07 +08:00
Steven 1dc4f02b64 chore: update memo requests 2024-01-27 20:35:48 +08:00
Steven 8db90a040c chore: remove unused dependencies 2024-01-27 20:20:36 +08:00
Steven 932f636d84 chore: update codeblock renderer 2024-01-27 19:09:10 +08:00
Steven ed32b20c9e chore: update frontend dependencies 2024-01-27 17:28:06 +08:00
Steven 10d709c167 chore: fix highlight cursor 2024-01-27 12:35:01 +08:00
Steven 8455114eef chore: fix list memos request 2024-01-27 11:17:59 +08:00
Steven c26109cd36 chore: update list memos request 2024-01-27 11:14:17 +08:00
Steven 4b223c1e4c chore: update collapse sidebar 2024-01-27 05:27:42 +08:00
Steven b9cbe6626f chore: update tag rename 2024-01-27 05:26:32 +08:00
Steven 566171783d chore: tweak embedded memo style 2024-01-26 23:33:40 +08:00
Steven 7edb3598ea chore: update default request limit 2024-01-26 23:03:10 +08:00
Steven bc2d2d0cde feat: support set embedded content in UI 2024-01-26 22:51:57 +08:00
Steven e1977df14b chore: remove check underscores 2024-01-26 21:23:36 +08:00
Wei Zhang ddc89029b7 fix: use mysql to parse dsn (#2838)
Signed-off-by: Zhang Wei <kweizh@gmail.com>
2024-01-26 13:43:48 +08:00
Steven f8b9a83d4a chore: tweak default value 2024-01-26 09:22:47 +08:00
Steven 2f16b7065a chore: tweak scripts 2024-01-26 09:15:27 +08:00
Steven e5ff1829a5 chore: add Hungarian locale 2024-01-26 08:44:12 +08:00
Steven d7889d9903 chore: tweak url filters 2024-01-26 08:39:53 +08:00
Steven db3457e081 chore: bump version 2024-01-26 08:30:22 +08:00
Steven 4f2b00b4f3 chore: add migration scripts 2024-01-26 08:29:11 +08:00
Steven 79558028c0 feat: implement rename tag 2024-01-25 23:09:35 +08:00
Steven 70d1301dc3 chore: use filter in url params 2024-01-25 20:05:47 +08:00
Steven 6d5e1def76 chore: update member section 2024-01-25 19:49:39 +08:00
boojack a5bc2d0ed6 chore: update i18n from Weblate (#2832)
* Added translation using Weblate (Hungarian)

* Translated using Weblate (Hungarian)

Currently translated at 0.3% (1 of 317 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/hu/

* Translated using Weblate (Hungarian)

Currently translated at 71.2% (226 of 317 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/hu/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (316 of 316 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/pt_BR/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (316 of 316 strings)

Translation: memos-i18n/i18n
Translate-URL: https://hosted.weblate.org/projects/memos-i18n/english/zh_Hant/

---------

Co-authored-by: Vermunds <com.github@weylus.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Lincoln Nogueira <lincolnthalles@users.noreply.github.com>
Co-authored-by: ti777777 <ti12272198686@yahoo.com.tw>
2024-01-25 19:36:02 +08:00
Steven 08ac60cc70 chore: update memo relation dialog 2024-01-25 08:59:10 +08:00
Mudkip f654d3c90e fix: encode filename when using url prefix for resources (#2829)
* fix: encode filename when using url prefix for resources

* fix: only encode the last parts of filename

* fix: encode all parts in filepath
2024-01-24 11:28:26 +08:00
Steven 1b69b73eb9 chore: update calendar styles 2024-01-23 23:15:54 +08:00
Steven 3dbb254aeb chore: update referenced memo title from params 2024-01-23 21:54:15 +08:00
Steven fdb1779a59 chore: implement referenced content renderer 2024-01-23 21:47:17 +08:00
Steven a316e239ce chore: implement referenced content node 2024-01-23 21:40:59 +08:00
Steven d7f02b94e5 chore: fix linter 2024-01-23 21:27:05 +08:00
Steven d165d87288 refactor: markdown parser matchers 2024-01-23 21:23:40 +08:00
Steven bf905bba86 chore: remove unused date picker 2024-01-22 22:50:27 +08:00
Brilliant Hanabi 3a129d5cfb fix: avoid making memos public when disabled (#2816)
* fix: avoid making memos public when disabled in v2

* fix: avoid making memos public when disabled in v1
2024-01-22 20:51:33 +08:00
ti777777 024a818e91 chore: update zh-Hant (#2815) 2024-01-21 23:50:44 +08:00
Steven 54a24833a7 chore: fix resource seeds 2024-01-21 21:36:31 +08:00
Steven a620d140c5 chore: update embedded content renderers 2024-01-21 21:27:04 +08:00
Steven 370054e040 chore: implement collapsed navigation 2024-01-21 21:02:55 +08:00
Steven fae0b4e900 chore: update memo relation style 2024-01-21 11:22:25 +08:00
Steven c38404b5d5 chore: tweak get memo by name 2024-01-21 10:57:53 +08:00
Steven 4d48f50815 chore: use resource name in frontend 2024-01-21 10:55:49 +08:00
Steven 582cc6609c feat: add user-defined id to resource 2024-01-21 10:49:30 +08:00
Steven 40bd75c725 fix: create memo with resource name 2024-01-21 10:33:31 +08:00
Steven b2fc3076f6 chore: update memo store 2024-01-21 01:23:55 +08:00
Steven 288527914b chore: migrate memo route 2024-01-21 01:23:44 +08:00
Steven a2aea3747c chore: remove server tests 2024-01-20 23:51:24 +08:00
Steven 8382354ef7 feat: add user-defined name to memo 2024-01-20 23:48:35 +08:00
Steven 264e6e6e9c chore: tweak file name 2024-01-20 23:47:04 +08:00
Steven eb72609ea3 chore: update memo editor cache key 2024-01-20 20:52:11 +08:00
Brilliant Hanabi 776785ac90 chore: update zh-Hans & zh-Hant translations (#2804) 2024-01-20 20:27:20 +08:00
Steven d5f874e185 chore: handle resource not found 2024-01-20 12:47:43 +08:00
Steven 89d940d9b7 feat: implement params field for embedded content node 2024-01-20 12:41:08 +08:00
Mark Zhao bd1cf62761 feat: enable iframe rendering in markdown code block (#2799)
* enable iframe rendering in code block

* fix eslint issue
2024-01-20 11:36:45 +08:00
Steven 196facfacd feat: implement embedded resource renderer 2024-01-20 09:17:31 +08:00
Steven afe75fd9f2 chore: fix tokens split tests 2024-01-20 02:09:33 +08:00
Steven 8a34013558 feat: implement embedded memo renderer 2024-01-20 01:56:10 +08:00
Steven 67f5ac3657 feat: implement subscript and superscript renderer 2024-01-19 23:10:16 +08:00
Steven 7236552b6c feat: implement subscript and superscript parsers 2024-01-19 23:06:22 +08:00
Steven 1f5899d238 chore: update dependencies 2024-01-19 19:20:59 +08:00
Wen Sun ec4884ea04 fix: incorrect timeline month display (#2792)
Fix incorrect timeline month display
2024-01-19 18:07:08 +08:00
Elliot Chen 2e0619b4dc feat: add a webhook action for deleted memos (#2791) 2024-01-19 09:56:00 +08:00
Steven c9146bc749 chore: update code style 2024-01-19 07:06:28 +08:00
Steven f5b5bd64bc chore: tweak datetime.ts 2024-01-18 22:18:56 +08:00
Steven d31d9eb71c chore: remove unused nil checks 2024-01-18 19:23:45 +08:00
Steven f28b654057 chore: fix setting nil checks 2024-01-18 19:20:48 +08:00
Steven 8738b68a44 chore: tweak readme 2024-01-18 19:06:49 +08:00
Steven 42381fa154 chore: add stable build action 2024-01-18 18:49:06 +08:00
Steven 22427101f8 chore: add stale issue action 2024-01-18 16:18:18 +08:00
Steven 2a4ebf5774 chore: add search bar in archived page 2024-01-18 15:28:37 +08:00
Steven 5172e4df7c chore: create memo visibility when creating 2024-01-18 15:20:22 +08:00
Steven 893dd2c85e chore: add disable filter to renderer context 2024-01-18 14:52:57 +08:00
Steven d426f89cf0 chore: add time filter to timeline 2024-01-18 14:30:20 +08:00
Steven 7de3de5610 chore: fix go test warning 2024-01-18 11:45:59 +08:00
Steven 2856e66609 chore: fix go test 2024-01-18 11:42:57 +08:00
Steven 354011f994 chore: handle filter in user profile page 2024-01-18 11:38:45 +08:00
Steven 8ed827cd2d chore: update table node delimiter 2024-01-18 11:36:13 +08:00
Steven 05c0aeb789 feat: implement table renderer 2024-01-18 10:49:28 +08:00
Steven aecffe3402 feat: implement table parser 2024-01-18 10:21:08 +08:00
Mehad Nadeem 70e6b2bb82 chore: added tooltip for vacuum button (#2778)
* chore: added tooltip for vacuum button

- At the moment only has text for English language therefore other JSON files need to be updated accordingly

* Update en.json
2024-01-18 08:09:49 +08:00
Steven 54296f0437 chore: update filter in timeline 2024-01-18 08:06:59 +08:00
Steven 8fcd9332f7 chore: tweak timeline styles 2024-01-17 22:32:58 +08:00
Steven 1857362d03 fix: locale and appearance setting 2024-01-17 22:16:12 +08:00
Steven 6d7186fc81 feat: rebuild timeline page 2024-01-17 21:59:49 +08:00
Wen Sun e4488da96e fix: signup is not allowed if password login is disabled (#2776)
Signup is not allowed if password login is disabled

If password login is disabled in the system configuration, the "signup" in the "/auth" page disappears, but the user can manually enter "/auth/signup" to access the system by creating a new user.
2024-01-17 10:49:22 +08:00
Steven cc43d06d33 chore: update memo stats response 2024-01-17 09:17:33 +08:00
Steven 9ffd827028 fix: appearance and locale initial value 2024-01-16 22:53:45 +08:00
Hanqin Guan 15e6542f0d fix: server overrides user's locale/appearance (#2771) 2024-01-16 18:21:08 +08:00
Noah Alderton 24bb3e096a fix: DatePicker by passing in Timezone to API (#2770)
* Fix DatePicker by passing in Timezone to API

* Add some clarity
2024-01-16 18:02:09 +08:00
Lincoln Nogueira 5bcbbd4c52 chore: fix store tests on Windows (#2769)
It's just a matter of explicitly closing the database, so that TempDir.removeAll doesn't fail.
2024-01-16 13:51:26 +08:00
Noah Alderton ff13d977e9 feat: add URL paste handler (#2768)
* feat: Add URL paste handler

* Check if text highlighted for URL pasting
2024-01-16 10:06:16 +08:00
Wen Sun 1fdb8b7b01 fix: apply customized profile name in mobile header (#2723)
Fix mobile header title, apply customized profile name
2024-01-16 09:04:03 +08:00
Wen Sun f1ee88c4e1 fix: display system logo in user banner if user not logged in (#2747)
Display system logo in user banner if user not logged in
2024-01-16 09:03:27 +08:00
Noah Alderton b578afbc6a fix: DatePicker Local Date (#2766)
Fix DatePicker Local Date
2024-01-16 08:35:48 +08:00
Steven ad94e8e3c6 feat: implement highlight renderer 2024-01-15 22:54:18 +08:00
Steven 3f4b361fad feat: implement highlight parser 2024-01-15 22:30:06 +08:00
Steven 46bd470640 chore: update favicon 2024-01-15 21:10:41 +08:00
Steven fdbf2d8af2 chore: fix blockquote renderer 2024-01-15 20:41:37 +08:00
Steven 5a723f00fa chore: split editor keydown handler 2024-01-15 20:33:42 +08:00
Noah Alderton 728a9705ea feat: Markdown Editor Keyboard Shortcuts (#2763)
* Add bold and italic keyboard shortcut

* Add hyperlink keyboard shortcut support
2024-01-15 20:19:59 +08:00
THELOSTSOUL cd3a98c095 fix: change use-set priority (#2760)
The user settings(locale, appearance) are not in use when restart broswer
2024-01-15 20:08:14 +08:00
Wen Sun a22ad90174 fix: set memo resources error in mysql (#2761)
Fix error updating memo resources in mysql
2024-01-15 20:05:07 +08:00
Steven 5ebbed9115 chore: handle tag click 2024-01-15 08:15:34 +08:00
Steven 7ae4299df2 chore: implement create resource 2024-01-15 08:13:06 +08:00
Noah Alderton 3d23c01e26 feat: add additional favicon formats (#2752)
Add additional favicons
2024-01-15 07:54:33 +08:00
Lincoln Nogueira 089e04bcfd chore: use webp compression on logo (#2756)
- Logo size reduced from 310 KB to 36 KB.
- Point metadata image URL to local logo instead of remote
2024-01-14 22:21:03 +08:00
Steven 98762be1e5 feat: implement indent for list nodes 2024-01-14 22:19:03 +08:00
Steven d44e74bd1e chore: update editor actions 2024-01-14 21:47:03 +08:00
Steven 8e0ce4d678 fix: list memos with pinned 2024-01-14 20:51:52 +08:00
Steven 45cf158508 chore: fix max width of home section 2024-01-14 20:25:45 +08:00
Anish Kelkar 7340ae15f7 chore: delete .vscode directory (#2693)
* Delete .vscode directory

* web/.vscode deleted
2024-01-14 20:12:57 +08:00
Steven 6db7ad76da chore: update tag selector 2024-01-13 16:26:42 +08:00
Steven 4a407668bc chore: tweak dialog close button 2024-01-13 15:33:23 +08:00
Steven ab1fa44f00 feat: implement markdown buttons 2024-01-13 15:09:06 +08:00
Steven cd0004cf88 chore: update icon version 2024-01-13 15:08:58 +08:00
Steven 667aaf06a0 chore: update dependencies 2024-01-13 14:33:50 +08:00
Steven a8074d94e8 chore: update image attrs 2024-01-13 14:08:36 +08:00
Hanqin Guan 16e68fbfff fix: duplicated/reflexive relation in v2 api. (#2750) 2024-01-13 07:21:19 +08:00
Wen Sun 81942b3b98 chore: updating the default scopes of GitHub SSO (#2746)
Updating the default scopes of GitHub SSO

The scope of "user" in GitHub OAuth includes permissions to update a user's profile.
2024-01-12 14:02:11 +08:00
Athurg Gooth a7cda28fc7 fix: filename with space (#2745) 2024-01-12 14:01:19 +08:00
Steven 0c52f1ee6a chore: tweak home style 2024-01-12 08:08:24 +08:00
Steven 1994c20c54 chore: tweak setting page 2024-01-11 22:25:05 +08:00
Steven a1dda913c3 chore: fix tag selector position 2024-01-11 21:36:22 +08:00
Athurg Gooth d626de1875 fix: pnpm install failed in docker (#2732)
fix pnpm install failed in docker
2024-01-11 21:29:55 +08:00
Wen Sun 6cfd94cc69 fix: deleting inbox records that senders have been deleted (#2743)
Deleting inbox records that senders have been deleted
2024-01-11 21:29:22 +08:00
Athurg Gooth 79b68222ff chore: set image loading to lazy (#2733)
set image loading to lazy to avoid concurrent problem
2024-01-11 10:27:18 +08:00
Steven aaec46a39c chore: update find memo with updated time 2024-01-10 00:10:59 +08:00
Steven 9c663b1ba2 fix: merge mysql dsn with params 2024-01-10 00:03:47 +08:00
Steven 777ed899a3 chore: add memo uid 2024-01-08 21:48:26 +08:00
Steven ddcf1d669d chore: add max content length 2024-01-08 21:17:21 +08:00
Steven 32d02ba022 chore: fix horizontal rule matcher 2024-01-08 21:00:45 +08:00
Steven 5449342016 fix: auto link converters 2024-01-08 20:57:44 +08:00
Noah Alderton 43e42079a4 feat: export Memos as Markdown FIles (#2716) 2024-01-08 11:40:50 +08:00
Steven cafa7c5adc chore: update backend dependencies 2024-01-06 19:46:35 +08:00
Steven 1258c5a5b0 chore: update workspace setting proto 2024-01-06 19:46:21 +08:00
Steven 83141f9be2 chore: tweak navigation styles 2024-01-06 19:12:26 +08:00
Steven 4c59035757 chore: update about page 2024-01-06 19:01:11 +08:00
Steven 9459ae8265 chore: update postgres stmt builder 2024-01-06 17:12:10 +08:00
Steven 8893a302e2 chore: update logs 2024-01-06 16:58:58 +08:00
Steven d67eaaaee2 chore: update database migrator 2024-01-06 16:55:13 +08:00
Steven fd8333eeda chore: fix memo parent_id 2024-01-06 13:22:02 +08:00
Steven f5a1739472 chore: update memo detail checks 2024-01-06 10:03:36 +08:00
Steven a297cc3140 chore: exclude comments in memo list response 2024-01-06 09:48:11 +08:00
Steven 79c13c6f83 chore: fix edit memo params 2024-01-06 09:25:17 +08:00
Steven 8b9455d784 chore: fix memo resources position 2024-01-06 09:23:20 +08:00
Steven 501f8898f6 chore: fix postgres stmts 2024-01-05 21:27:16 +08:00
Steven ee13927607 chore: fix restore tag node 2024-01-05 18:50:28 +08:00
Steven d2a9aaa9d4 chore: update line break renderer 2024-01-05 09:18:37 +08:00
Steven f563b58a85 chore: fix renderer props 2024-01-05 08:47:43 +08:00
Steven ce2d37b90c chore: fix find sibling node 2024-01-05 08:43:30 +08:00
Steven 454cd4e24f feat: implement switchable task list node 2024-01-05 08:40:16 +08:00
Steven 6320d042c8 chore: update home padding styles 2024-01-04 22:50:46 +08:00
Steven d7ed59581c chore: fix math block matcher 2024-01-04 21:50:13 +08:00
Steven 9593b0b091 chore: fix link rel field 2024-01-04 21:26:56 +08:00
Steven ca53630410 chore: update drawer background 2024-01-04 21:11:22 +08:00
Steven f484c38745 chore: fix dependencies 2024-01-04 20:07:53 +08:00
Steven d12a2b0c38 feat: implement math expression parser 2024-01-04 20:05:29 +08:00
Steven c842b921bc chore: update backend dependencies 2024-01-04 19:15:25 +08:00
Wen Sun 6b2eec86c2 fix: image upload failed with cloudflare R2 (#2704)
Fix image upload failed with cloudflare R2
2024-01-04 19:08:54 +08:00
Steven 2eba4e2cd4 chore: update version 2024-01-04 08:36:45 +08:00
Steven 73baeaa0ad chore: tweak dark mode styles 2024-01-04 08:32:14 +08:00
Steven c58851bc97 chore: tweak accent color 2024-01-03 23:30:28 +08:00
Steven 96140f3875 chore: tweak dark mode styles 2024-01-03 23:12:50 +08:00
Lincoln Nogueira 369b8af109 chore: improve resource internal_path migrator (#2698)
* chore: improve internal path migrator
- handle mixed path styles
- handle Windows paths
- add tests

* chore: fix goimports error
2024-01-03 08:31:59 +08:00
Steven 914c0620c4 chore: add statistics view 2024-01-03 08:22:32 +08:00
Steven 138b69e36e chore: fix memo comment 2024-01-03 08:19:38 +08:00
Elliot Chen 3181c076b2 feat: add {uuid} in path template when using local storage or S3 (#2696)
Add {uuid} in path template when using local storage or S3

Add an addition tag `{uuid}` to the `replacePathTemplate`.

It is a workaround to leak the public links of a resource when using S3-based object storage. Currently, all resource blobs stored in S3 (R2, OSS) are set to be public. It is insecure as the resources for the private memos are also accessible on the Internet. Using an additional {uuid} might reduce this risk.

Meanwhile, it is also possible to avoid filename conflict
2024-01-02 20:57:55 +08:00
Noah Alderton 673809e07d fix: docker-compose.dev.yaml (#2695)
* Fix docker-compose.dev.yaml

* Add newline to .gitignore
2024-01-02 18:33:19 +08:00
Steven f74fa97b4a chore: traverse nodes to upsert tags 2024-01-02 08:56:30 +08:00
Steven c797099950 chore: update resource internal path migrator 2024-01-02 08:29:18 +08:00
Steven 0f8bfb6328 chore: update index.html 2023-12-29 08:28:17 +08:00
Steven 4cd01ece30 chore: update frontend metadata 2023-12-29 08:19:32 +08:00
Lincoln Nogueira 14b34edca3 chore: fix misuse of package path instead of filepath.path (#2684)
As stated by https://pkg.go.dev/path, "path" is mainly for URLs, "path.filepath" for file systems
2023-12-29 07:50:15 +08:00
Lincoln Nogueira 411e807dcc chore: use consistent relative paths for resources (#2683)
- always store resources with a relative path with forward slashes, which will be transformed as needed when the file is accessed

- fix an issue with thumbnail generation on Windows

- add several validations for local storage setting

- improve front-end error feedback when changing local storage

- add migrations to make existing resource paths relative (not needed, but improves database consistency)
2023-12-29 07:49:55 +08:00
Steven ea87a1dc0c chore: update memo content props 2023-12-28 22:57:51 +08:00
Steven 46f7cffc7b feat: implement restore nodes 2023-12-28 22:35:39 +08:00
Steven 2a6f054876 chore: update auto link parser 2023-12-28 21:32:44 +08:00
Steven 30dca18b79 chore: fix suspense wrapper 2023-12-28 08:34:06 +08:00
Steven 09c195c752 chore: update backend dependencies 2023-12-28 08:28:50 +08:00
Steven 2ae6d94e2c chore: update frontend dependencies 2023-12-28 08:27:06 +08:00
Steven 9ee4b75bbd chore: tweak memo detail styles 2023-12-27 23:25:02 +08:00
Steven cc40803b06 chore: update feature request template 2023-12-27 09:00:16 +08:00
Steven a0a03b0389 chore: tweak list memos request 2023-12-27 08:54:00 +08:00
Steven 0dfc367e56 chore: start grpc server 2023-12-27 08:50:02 +08:00
Steven c8d7f93dca feat: implement auto link parser 2023-12-27 08:44:51 +08:00
Steven 6fac116d8c chore: update user store 2023-12-26 23:05:33 +08:00
subks f48ff102c9 fix: eslint check failure after fixing #2672 (#2673) 2023-12-26 21:46:26 +08:00
subks bd5a0679ee fix: date format in share memo dialog (#2672)
fix: Date format in shareable Memo Images (#2668)
2023-12-26 17:50:09 +08:00
Steven fcfb76a103 chore: remove user urlsets in sitemap 2023-12-23 19:35:46 +08:00
Steven 8e325f9986 chore: return username in user response 2023-12-23 19:23:39 +08:00
Steven b8eaf1d57e chore: deprecate memo creation stats legacy api 2023-12-23 18:35:47 +08:00
Steven 42608cdd8f chore: fix server context 2023-12-23 17:59:15 +08:00
Steven 2cfa4c3b76 chore: tweak frontend routes register 2023-12-23 17:58:49 +08:00
Steven aa136a2776 chore: remove vite pwa plugin 2023-12-23 17:42:13 +08:00
Steven 68413a5371 chore: update frontend service 2023-12-23 17:04:52 +08:00
Steven 638f17a02c chore: update scripts 2023-12-23 15:12:25 +08:00
Steven 273d6a6986 chore: update dockerfile 2023-12-23 14:13:40 +08:00
Steven 953141813c chore: regenerate pnpm lock file 2023-12-23 12:01:26 +08:00
Leyang be2db3f170 feat: use vite plugin pwa for generate right sw.js (#2658)
Co-authored-by: boojack <stevenlgtm@gmail.com>
2023-12-23 12:00:03 +08:00
Steven eefce6ade3 chore: implement webhook dispatch in v2 api 2023-12-23 11:17:35 +08:00
Steven c6ebb5552e chore: add 403 page 2023-12-23 10:08:23 +08:00
Steven 4d64d4bf25 chore: upgrade frontend dependencies 2023-12-23 09:47:43 +08:00
Steven 2ee4d7d745 chore: add startTransition to links 2023-12-23 08:55:43 +08:00
Steven 1b81999329 chore: skip invalida setting checks 2023-12-23 08:55:23 +08:00
Steven df5aeb6d88 chore: remove v1 prefix in store name 2023-12-23 08:48:11 +08:00
Steven df3303dcd3 chore: update list users 2023-12-23 08:35:54 +08:00
Steven c267074851 chore: prevent archive/delete current user 2023-12-23 08:05:05 +08:00
Steven 21874d0509 chore: fix navigation errors 2023-12-23 08:01:16 +08:00
Steven 7898df2876 chore: update issue templates 2023-12-23 08:00:59 +08:00
Steven b2ec0d1217 chore: add skip cache requesting 2023-12-22 20:30:28 +08:00
Steven 5673e29e51 chore: compose memo in backend 2023-12-22 20:18:31 +08:00
Steven feefaabce9 chore: update heatmap click handler 2023-12-22 20:07:17 +08:00
Steven 29b540ade3 chore: fix fetch memos 2023-12-22 19:47:46 +08:00
Steven 919f75af1a chore: update suspense loading 2023-12-22 19:27:09 +08:00
Steven 17e905085f chore: update acl config 2023-12-22 09:11:55 +08:00
Steven 34af969785 chore: fix list memos order by pinned 2023-12-22 09:09:03 +08:00
Steven fd9c3ccbae chore: implement useMemoList store 2023-12-22 09:01:30 +08:00
Steven a3feeceace chore: remove component v1 suffix 2023-12-22 08:29:02 +08:00
Steven 02265a6e1a chore: fix memo search 2023-12-22 00:31:29 +08:00
Steven 81524c38e9 chore: refactor memo module 2023-12-21 23:40:43 +08:00
Steven 671551bdc1 chore: update memo detail page 2023-12-21 22:42:06 +08:00
Steven 10c81ccba3 chore: fix type definition 2023-12-21 21:43:28 +08:00
Steven 9361613f23 chore: update timeline page 2023-12-21 21:24:08 +08:00
Athurg Gooth b14334220f fix: trim the dirname of attachment send by telegram bot (#2651) 2023-12-21 09:47:57 +08:00
Athurg Gooth f184d65267 fix: attachments send from telegram lost (#2650) 2023-12-21 09:47:25 +08:00
Steven b64e2ff6ff chore: implement list memo resources api 2023-12-20 23:46:04 +08:00
Steven cbdae24314 chore: update archived page 2023-12-20 23:23:26 +08:00
Steven 762cb25227 chore: update memo service 2023-12-20 23:14:15 +08:00
Steven fc01a796f8 chore: fix demo seed data 2023-12-20 08:18:56 +08:00
Steven feb700f325 chore: clear access token when user not found 2023-12-20 07:42:02 +08:00
Steven 5334fdf1b2 chore: use api v2 in archived page 2023-12-19 23:49:24 +08:00
Steven abc14217f6 chore: tweak padding styles 2023-12-19 23:09:57 +08:00
Steven af68cae6ea chore: regenerate swagger docs 2023-12-19 22:37:07 +08:00
Steven e0cacfc6d6 chore: retire auto backup for sqlite 2023-12-19 22:34:06 +08:00
Steven b575064d47 chore: tweak padding 2023-12-19 21:44:40 +08:00
Steven 6290234ad1 chore: fix button styles 2023-12-19 21:29:07 +08:00
Steven aeed25648a chore: tweak drawer background 2023-12-19 08:55:21 +08:00
Steven 43e7506ed5 chore: fix react-uses import 2023-12-19 08:45:47 +08:00
Steven a3a1bbe8de chore: tweak responsible styles 2023-12-19 08:41:41 +08:00
Steven fe4ec0b156 chore: rename navigator 2023-12-19 00:13:22 +08:00
Steven 7c5fdd1b06 chore: remove demo banner 2023-12-18 23:46:48 +08:00
Steven 4d54463aeb chore: add mobile header 2023-12-18 23:33:09 +08:00
Steven 40bc8df63d chore: fix container height 2023-12-18 23:01:39 +08:00
Steven 61de7c8a32 chore: fix demo banner 2023-12-18 22:29:29 +08:00
Steven d6656db20d feat: implement part of full-screen layout 2023-12-18 22:10:36 +08:00
Steven 15a091fe4c chore: fix offset params in explore page 2023-12-18 20:54:51 +08:00
Steven d8a0528135 chore: tweak variable names 2023-12-18 20:47:29 +08:00
Steven 16fb5faebd chore: revert go mod update 2023-12-18 19:40:39 +08:00
Steven 2c4b5d75b3 chore: fix html escaping 2023-12-17 23:37:00 +08:00
Steven 770607f93f fix: add markdown service to acl 2023-12-17 11:37:38 +08:00
Steven db0eff4743 chore: clean frontend dependencies 2023-12-17 11:25:10 +08:00
Steven 0793f96578 chore: update heading styles 2023-12-17 11:08:13 +08:00
Steven 8095d94c97 chore: deprecate marked 2023-12-17 11:02:16 +08:00
Steven bcfcd59642 chore: deprecate old MemoContent 2023-12-17 10:58:22 +08:00
Steven 5d677c3c57 chore: implement node renderer components 2023-12-17 10:49:49 +08:00
Steven 28c0549705 feat: add markdown service 2023-12-17 09:53:22 +08:00
Steven bb42042db4 chore: implement task list parser 2023-12-16 12:48:52 +08:00
Steven 1c7fb77e05 chore: update user setting names 2023-12-16 12:18:53 +08:00
Steven e8ca2ea5a0 chore: rename renderer package 2023-12-16 11:57:36 +08:00
Steven e43a445c34 chore: implement escaping character node 2023-12-16 11:47:29 +08:00
Steven 1237643028 chore: update parser tests 2023-12-16 11:34:55 +08:00
Steven aee0e31b0a chore: update parser functions 2023-12-16 10:38:05 +08:00
Steven 47af632c79 chore: update inline parser 2023-12-16 10:09:20 +08:00
Steven 7b0ceee57b chore: update memo metadata description 2023-12-16 09:23:45 +08:00
Steven bdc867d153 fix: heading render 2023-12-16 09:12:55 +08:00
Steven 6421fbc68a chore: implement list html render 2023-12-16 09:01:19 +08:00
Steven b00443c222 chore: implement list nodes 2023-12-16 08:51:29 +08:00
Steven a10b3d3821 chore: tweak custom profile 2023-12-15 22:57:53 +08:00
Steven 7735cfac31 chore: update seed data 2023-12-15 22:34:19 +08:00
727 changed files with 55044 additions and 51984 deletions
+11 -3
View File
@@ -1,11 +1,11 @@
name: Bug Report
description: Create a report to help us improve
description: If something isn't working as expected
labels: [bug]
body:
- type: markdown
attributes:
value: |
If you are reporting a new issue, make sure that we do not have any duplicates already open. You can ensure this by searching the issue list for this repository. If there is a duplicate, please close your issue and add a comment to the existing issue instead.
Before submitting a bug report, please check if the issue is already present in the issues. If it is, please add a reaction to the issue. If it isn't, please fill out the form below.
- type: textarea
attributes:
label: Describe the bug
@@ -24,8 +24,16 @@ body:
3. See error
validations:
required: true
- type: input
attributes:
label: |
The version of Memos you're using.
description: |
Provide the version of Memos you're using. Please use the following format: `v0.22.0` instead of `stable` or `latest`.
validations:
required: true
- type: textarea
attributes:
label: Screenshots or additional context
description: |
Add screenshots or any other context about the problem.
If applicable, add screenshots to help explain your problem. And add any other context about the problem here. Such as the device you're using, etc.
+20 -12
View File
@@ -1,28 +1,36 @@
name: Feature Request
description: Suggest an idea for this project
description: If you have a suggestion for a new feature
labels: [enhancement]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to suggest an idea for memos!
- type: textarea
attributes:
label: Is your feature request related to a problem?
description: |
A clear and concise description of what the problem is.
placeholder: |
I'm always frustrated when [...]
validations:
required: true
Before submitting a feature request, please check if the issue is already present in the issues. If it is, please add a reaction to the issue. If it isn't, please fill out the form below.
- type: textarea
attributes:
label: Describe the solution you'd like
description: |
A clear and concise description of what you want to happen.
placeholder: |
It would be great if [...]
validations:
required: true
- type: dropdown
attributes:
label: Type of feature
description: What type of feature is this?
options:
- User Interface (UI)
- User Experience (UX)
- API
- Documentation
- Integrations
- Other
default: 0
validations:
required: true
- type: textarea
attributes:
label: Additional context
description: Add any other context or screenshots about the feature request.
description: |
What are you trying to do? Why is this important to you?
+20
View File
@@ -0,0 +1,20 @@
version: 2
updates:
- package-ecosystem: "github-actions"
commit-message:
prefix: "chore"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: npm
commit-message:
prefix: "chore"
directory: "/web"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
commit-message:
prefix: "chore"
directory: "/"
schedule:
interval: "weekly"
+4 -4
View File
@@ -19,15 +19,15 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: 1.21
go-version: 1.22
check-latest: true
cache: true
- name: Verify go.mod is tidy
run: |
go mod tidy -go=1.21
go mod tidy -go=1.22
git diff --exit-code
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v6
with:
version: v1.54.1
args: --verbose --timeout=3m
@@ -39,7 +39,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: 1.21
go-version: 1.22
check-latest: true
cache: true
- name: Run all tests
@@ -27,8 +27,8 @@ jobs:
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: neosmemo
password: ${{ secrets.DOCKER_NEOSMEMO_TOKEN }}
username: stevenlgtm
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
@@ -52,13 +52,12 @@ jobs:
neosmemo/memos
ghcr.io/usememos/memos
tags: |
type=raw,value=latest
type=semver,pattern={{version}},value=${{ env.VERSION }}
type=semver,pattern={{major}}.{{minor}},value=${{ env.VERSION }}
- name: Build and Push
id: docker_build
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
context: ./
file: ./Dockerfile
@@ -0,0 +1,61 @@
name: build-and-push-stable-image
on:
push:
branches:
- "stable"
jobs:
build-and-push-release-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: stevenlgtm
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
with:
install: true
version: v0.9.1
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
neosmemo/memos
ghcr.io/usememos/memos
tags: |
type=raw,value=stable
flavor: |
latest=true
- name: Build and Push
id: docker_build
uses: docker/build-push-action@v6
with:
context: ./
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
@@ -19,8 +19,8 @@ jobs:
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: neosmemo
password: ${{ secrets.DOCKER_NEOSMEMO_TOKEN }}
username: stevenlgtm
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
@@ -50,7 +50,7 @@ jobs:
- name: Build and Push
id: docker_build
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
context: ./
file: ./Dockerfile
+45
View File
@@ -0,0 +1,45 @@
name: Build artifacts
on:
push:
tags:
- "*"
permissions:
contents: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-go@v5
with:
go-version: 1.22
check-latest: true
cache: true
- uses: pnpm/action-setup@v4.0.0
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: pnpm
cache-dependency-path: "web/pnpm-lock.yaml"
- run: pnpm install
working-directory: web
- run: pnpm release
working-directory: web
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
# either 'goreleaser' (default) or 'goreleaser-pro'
distribution: goreleaser
# 'latest', 'nightly', or a semver
version: latest
args: release --clean --skip=validate
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-74
View File
@@ -1,74 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [main]
pull_request:
# The branches below must be a subset of the branches above
branches: [main]
paths:
- "go.mod"
- "go.sum"
- "**.go"
- "proto/**"
- "web/**"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: ["go", "javascript"]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
+8 -9
View File
@@ -11,13 +11,13 @@ on:
- "web/**"
jobs:
eslint-checks:
static-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2.4.0
- uses: pnpm/action-setup@v4.0.0
with:
version: 8
version: 9
- uses: actions/setup-node@v4
with:
node-version: "20"
@@ -25,19 +25,20 @@ jobs:
cache-dependency-path: "web/pnpm-lock.yaml"
- run: pnpm install
working-directory: web
- run: pnpm type-gen
working-directory: web
- name: Run eslint check
run: pnpm lint
working-directory: web
- name: Run type checks
run: pnpm type-check
working-directory: web
frontend-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2.4.0
- uses: pnpm/action-setup@v4.0.0
with:
version: 8
version: 9
- uses: actions/setup-node@v4
with:
node-version: "20"
@@ -45,8 +46,6 @@ jobs:
cache-dependency-path: "web/pnpm-lock.yaml"
- run: pnpm install
working-directory: web
- run: pnpm type-gen
working-directory: web
- name: Run frontend build
run: pnpm build
working-directory: web
+17
View File
@@ -0,0 +1,17 @@
name: Close Stale Issues
on:
schedule:
- cron: "0 */8 * * *"
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/stale@v9.0.0
with:
days-before-issue-stale: 14
days-before-issue-close: 7
+10 -1
View File
@@ -6,7 +6,6 @@ tmp
# Frontend asset
web/dist
server/frontend/dist
# build folder
build
@@ -16,4 +15,14 @@ build
# Jetbrains
.idea
# Docker Compose Environment File
.env
bin/air
dev-dist
dist
# VSCode settings
/.vscode
+2
View File
@@ -73,6 +73,8 @@ linters-settings:
disabled: true
- name: unhandled-error
disabled: true
- name: if-return
disabled: true
gocritic:
disabled-checks:
- ifElseChain
+38
View File
@@ -0,0 +1,38 @@
version: 1
before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy
builds:
- env:
- CGO_ENABLED=0
main: ./bin/memos
binary: memos
goos:
- linux
- darwin
archives:
- format: tar.gz
# this name template makes the OS and Arch compatible with the results of `uname`.
name_template: >-
{{ .ProjectName }}_{{ .Tag }}_{{ .Os }}_{{ .Arch }}
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
checksum:
disable: true
release:
draft: true
replace_existing_draft: true
make_latest: true
mode: replace
skip_upload: false
-3
View File
@@ -1,3 +0,0 @@
{
"recommendations": ["golang.go"]
}
-12
View File
@@ -1,12 +0,0 @@
{
"folders": [
{
"name": "server",
"path": "../"
},
{
"name": "web",
"path": "../web"
}
]
}
-5
View File
@@ -1,5 +0,0 @@
{
"json.schemaDownload.enable":true,
"go.lintOnSave": "workspace",
"go.lintTool": "golangci-lint",
}
+3 -3
View File
@@ -6,16 +6,16 @@ COPY . .
WORKDIR /frontend-build/web
RUN corepack enable && pnpm i --frozen-lockfile && pnpm type-gen
RUN corepack enable && pnpm i --frozen-lockfile
RUN pnpm build
# Build backend exec file.
FROM golang:1.21-alpine AS backend
FROM golang:1.22-alpine AS backend
WORKDIR /backend-build
COPY . .
COPY --from=frontend /frontend-build/web/dist ./server/frontend/dist
COPY --from=frontend /frontend-build/web/dist /backend-build/server/router/frontend/dist
RUN CGO_ENABLED=0 go build -o memos ./bin/memos/main.go
+17 -22
View File
@@ -8,17 +8,15 @@ A privacy-first, lightweight note-taking service. Easily capture and share your
<a href="https://demo.usememos.com/">Live Demo</a>
<p>
<a href="https://github.com/usememos/memos/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/usememos/memos?logo=github" /></a>
<a href="https://hub.docker.com/r/neosmemo/memos"><img alt="Docker pull" src="https://img.shields.io/docker/pulls/neosmemo/memos.svg"/></a>
<a href="https://hosted.weblate.org/engage/memos-i18n/"><img src="https://hosted.weblate.org/widget/memos-i18n/english/svg-badge.svg" alt="Translation status" /></a>
<a href="https://discord.gg/tfPJa4UmAv"><img alt="Discord" src="https://img.shields.io/badge/discord-chat-5865f2?logo=discord&logoColor=f5f5f5" /></a>
</p>
![demo](https://www.usememos.com/demo.webp)
![demo](https://www.usememos.com/demo.png)
## Key points
- **Open source and free forever**. Embrace a future where creativity knows no boundaries with our open-source solution free today, tomorrow, and always.
- **Open source and free to use**. Embrace a future where creativity knows no boundaries with our open-source solution. All features are free to use and will never be charged in any form or content.
- **Self-hosting with Docker in just seconds**. Enjoy the flexibility, scalability, and ease of setup that Docker provides, allowing you to have full control over your data and privacy.
- **Pure text with added Markdown support.** Say goodbye to the overwhelming mental burden of rich formatting and embrace a minimalist approach.
- **Customize and share your notes effortlessly**. With our intuitive sharing features, you can easily collaborate and distribute your notes with others.
@@ -27,9 +25,12 @@ A privacy-first, lightweight note-taking service. Easily capture and share your
## Deploy with Docker in seconds
```bash
docker run -d --name memos -p 5230:5230 -v ~/.memos/:/var/opt/memos ghcr.io/usememos/memos:latest
docker run -d --name memos -p 5230:5230 -v ~/.memos/:/var/opt/memos neosmemo/memos:stable
```
> [!NOTE]
> This command is only applicable for Unix/Linux systems. For Windows, please refer to the detailed [documentation](https://www.usememos.com/docs/install/self-hosting).
>
> The `~/.memos/` directory will be used as the data directory on your local machine, while `/var/opt/memos` is the directory of the volume in Docker and should not be modified.
Learn more about [other installation methods](https://www.usememos.com/docs/install).
@@ -38,25 +39,19 @@ Learn more about [other installation methods](https://www.usememos.com/docs/inst
Contributions are what make the open-source community such an amazing place to learn, inspire, and create. We greatly appreciate any contributions you make. Thank you for being a part of our community! 🥰
<a href="https://github.com/usememos/memos/graphs/contributors">
<img src="https://contri-graphy.yourselfhosted.com/graph?repo=usememos/memos&format=svg" />
## Internationalization
Memos supports multiple languages. You can help us translate Memos into your language. We use Weblate to manage translations.
<a href="https://hosted.weblate.org/engage/memos-i18n/">
<img src="https://hosted.weblate.org/widget/memos-i18n/english/287x66-grey.png" alt="Translation status" />
</a>
---
- [Moe Memos](https://memos.moe/) - Third party client for iOS and Android
- [lmm214/memos-bber](https://github.com/lmm214/memos-bber) - Chrome extension
- [Rabithua/memos_wmp](https://github.com/Rabithua/memos_wmp) - WeChat MiniProgram
- [qazxcdswe123/telegramMemoBot](https://github.com/qazxcdswe123/telegramMemoBot) - Telegram bot
- [eallion/memos.top](https://github.com/eallion/memos.top) - Static page rendered with the Memos API
- [eindex/logseq-memos-sync](https://github.com/EINDEX/logseq-memos-sync) - Logseq plugin
- [quanru/obsidian-periodic-para](https://github.com/quanru/obsidian-periodic-para#daily-record) - Obsidian plugin
- [JakeLaoyu/memos-import-from-flomo](https://github.com/JakeLaoyu/memos-import-from-flomo) - Import data. Support from flomo, wechat reading
- [Quick Memo](https://www.icloud.com/shortcuts/1eaef307112843ed9f91d256f5ee7ad9) - Shortcuts (iOS, iPadOS or macOS)
- [Memos Raycast Extension](https://www.raycast.com/JakeYu/memos) - Raycast extension
- [Memos Desktop](https://github.com/xudaolong/memos-desktop) - Third party client for MacOS and Windows
- [MemosGallery](https://github.com/BarryYangi/MemosGallery) - A static Gallery rendered with the Memos API
## Star history
[![Star History Chart](https://api.star-history.com/svg?repos=usememos/memos&type=Date)](https://star-history.com/#usememos/memos&Date)
## Other projects
- [**Slash**](https://github.com/yourselfhosted/slash): An open source, self-hosted bookmarks and link sharing platform. Save and share your links very easily.
- [**Gomark**](https://github.com/usememos/gomark): A markdown parser written in Go for Memos. And its [WebAssembly version](https://github.com/usememos/gomark-wasm) is also available.
-165
View File
@@ -1,165 +0,0 @@
package resource
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"sync/atomic"
"time"
"github.com/disintegration/imaging"
"github.com/labstack/echo/v4"
"github.com/pkg/errors"
"go.uber.org/zap"
"github.com/usememos/memos/internal/log"
"github.com/usememos/memos/internal/util"
"github.com/usememos/memos/server/profile"
"github.com/usememos/memos/store"
)
const (
// The key name used to store user id in the context
// user id is extracted from the jwt token subject field.
userIDContextKey = "user-id"
// thumbnailImagePath is the directory to store image thumbnails.
thumbnailImagePath = ".thumbnail_cache"
)
type Service struct {
Profile *profile.Profile
Store *store.Store
}
func NewService(profile *profile.Profile, store *store.Store) *Service {
return &Service{
Profile: profile,
Store: store,
}
}
func (s *Service) RegisterResourcePublicRoutes(g *echo.Group) {
g.GET("/r/:resourceId", s.streamResource)
g.GET("/r/:resourceId/*", s.streamResource)
}
func (s *Service) streamResource(c echo.Context) error {
ctx := c.Request().Context()
resourceID, err := util.ConvertStringToInt32(c.Param("resourceId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("resourceId"))).SetInternal(err)
}
resource, err := s.Store.GetResource(ctx, &store.FindResource{
ID: &resourceID,
GetBlob: true,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find resource by ID: %v", resourceID)).SetInternal(err)
}
if resource == nil {
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Resource not found: %d", resourceID))
}
// Check the related memo visibility.
if resource.MemoID != nil {
memo, err := s.Store.GetMemo(ctx, &store.FindMemo{
ID: resource.MemoID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find memo by ID: %v", resource.MemoID)).SetInternal(err)
}
if memo != nil && memo.Visibility != store.Public {
userID, ok := c.Get(userIDContextKey).(int32)
if !ok || (memo.Visibility == store.Private && userID != resource.CreatorID) {
return echo.NewHTTPError(http.StatusUnauthorized, "Resource visibility not match")
}
}
}
blob := resource.Blob
if resource.InternalPath != "" {
resourcePath := resource.InternalPath
src, err := os.Open(resourcePath)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to open the local resource: %s", resourcePath)).SetInternal(err)
}
defer src.Close()
blob, err = io.ReadAll(src)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to read the local resource: %s", resourcePath)).SetInternal(err)
}
}
if c.QueryParam("thumbnail") == "1" && util.HasPrefixes(resource.Type, "image/png", "image/jpeg") {
ext := filepath.Ext(resource.Filename)
thumbnailPath := filepath.Join(s.Profile.Data, thumbnailImagePath, fmt.Sprintf("%d%s", resource.ID, ext))
thumbnailBlob, err := getOrGenerateThumbnailImage(blob, thumbnailPath)
if err != nil {
log.Warn(fmt.Sprintf("failed to get or generate local thumbnail with path %s", thumbnailPath), zap.Error(err))
} else {
blob = thumbnailBlob
}
}
c.Response().Writer.Header().Set(echo.HeaderCacheControl, "max-age=3600")
c.Response().Writer.Header().Set(echo.HeaderContentSecurityPolicy, "default-src 'none'; script-src 'none'; img-src 'self'; media-src 'self'; sandbox;")
c.Response().Writer.Header().Set("Content-Disposition", fmt.Sprintf(`filename="%s"`, resource.Filename))
resourceType := strings.ToLower(resource.Type)
if strings.HasPrefix(resourceType, "text") {
resourceType = echo.MIMETextPlainCharsetUTF8
} else if strings.HasPrefix(resourceType, "video") || strings.HasPrefix(resourceType, "audio") {
http.ServeContent(c.Response(), c.Request(), resource.Filename, time.Unix(resource.UpdatedTs, 0), bytes.NewReader(blob))
return nil
}
return c.Stream(http.StatusOK, resourceType, bytes.NewReader(blob))
}
var availableGeneratorAmount int32 = 32
func getOrGenerateThumbnailImage(srcBlob []byte, dstPath string) ([]byte, error) {
if _, err := os.Stat(dstPath); err != nil {
if !errors.Is(err, os.ErrNotExist) {
return nil, errors.Wrap(err, "failed to check thumbnail image stat")
}
if atomic.LoadInt32(&availableGeneratorAmount) <= 0 {
return nil, errors.New("not enough available generator amount")
}
atomic.AddInt32(&availableGeneratorAmount, -1)
defer func() {
atomic.AddInt32(&availableGeneratorAmount, 1)
}()
reader := bytes.NewReader(srcBlob)
src, err := imaging.Decode(reader, imaging.AutoOrientation(true))
if err != nil {
return nil, errors.Wrap(err, "failed to decode thumbnail image")
}
thumbnailImage := imaging.Resize(src, 512, 0, imaging.Lanczos)
dstDir := path.Dir(dstPath)
if err := os.MkdirAll(dstDir, os.ModePerm); err != nil {
return nil, errors.Wrap(err, "failed to create thumbnail dir")
}
if err := imaging.Save(thumbnailImage, dstPath); err != nil {
return nil, errors.Wrap(err, "failed to resize thumbnail image")
}
}
dstFile, err := os.Open(dstPath)
if err != nil {
return nil, errors.Wrap(err, "failed to open the local resource")
}
defer dstFile.Close()
dstBlob, err := io.ReadAll(dstFile)
if err != nil {
return nil, errors.Wrap(err, "failed to read the local resource")
}
return dstBlob, nil
}
-399
View File
@@ -1,399 +0,0 @@
package v1
import (
"context"
"encoding/json"
"fmt"
"net/http"
"regexp"
"strings"
"time"
"github.com/labstack/echo/v4"
"github.com/pkg/errors"
"golang.org/x/crypto/bcrypt"
"github.com/usememos/memos/api/auth"
"github.com/usememos/memos/internal/util"
"github.com/usememos/memos/plugin/idp"
"github.com/usememos/memos/plugin/idp/oauth2"
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
)
var (
usernameMatcher = regexp.MustCompile("^[a-z0-9]([a-z0-9-]{1,30}[a-z0-9])$")
)
type SignIn struct {
Username string `json:"username"`
Password string `json:"password"`
Remember bool `json:"remember"`
}
type SSOSignIn struct {
IdentityProviderID int32 `json:"identityProviderId"`
Code string `json:"code"`
RedirectURI string `json:"redirectUri"`
}
type SignUp struct {
Username string `json:"username"`
Password string `json:"password"`
}
func (s *APIV1Service) registerAuthRoutes(g *echo.Group) {
g.POST("/auth/signin", s.SignIn)
g.POST("/auth/signin/sso", s.SignInSSO)
g.POST("/auth/signout", s.SignOut)
g.POST("/auth/signup", s.SignUp)
}
// SignIn godoc
//
// @Summary Sign-in to memos.
// @Tags auth
// @Accept json
// @Produce json
// @Param body body SignIn true "Sign-in object"
// @Success 200 {object} store.User "User information"
// @Failure 400 {object} nil "Malformatted signin request"
// @Failure 401 {object} nil "Password login is deactivated | Incorrect login credentials, please try again"
// @Failure 403 {object} nil "User has been archived with username %s"
// @Failure 500 {object} nil "Failed to find system setting | Failed to unmarshal system setting | Incorrect login credentials, please try again | Failed to generate tokens | Failed to create activity"
// @Router /api/v1/auth/signin [POST]
func (s *APIV1Service) SignIn(c echo.Context) error {
ctx := c.Request().Context()
signin := &SignIn{}
disablePasswordLoginSystemSetting, err := s.Store.GetSystemSetting(ctx, &store.FindSystemSetting{
Name: SystemSettingDisablePasswordLoginName.String(),
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find system setting").SetInternal(err)
}
if disablePasswordLoginSystemSetting != nil {
disablePasswordLogin := false
err = json.Unmarshal([]byte(disablePasswordLoginSystemSetting.Value), &disablePasswordLogin)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal system setting").SetInternal(err)
}
if disablePasswordLogin {
return echo.NewHTTPError(http.StatusUnauthorized, "Password login is deactivated")
}
}
if err := json.NewDecoder(c.Request().Body).Decode(signin); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted signin request").SetInternal(err)
}
user, err := s.Store.GetUser(ctx, &store.FindUser{
Username: &signin.Username,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Incorrect login credentials, please try again")
}
if user == nil {
return echo.NewHTTPError(http.StatusUnauthorized, "Incorrect login credentials, please try again")
} else if user.RowStatus == store.Archived {
return echo.NewHTTPError(http.StatusForbidden, fmt.Sprintf("User has been archived with username %s", signin.Username))
}
// Compare the stored hashed password, with the hashed version of the password that was received.
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(signin.Password)); err != nil {
// If the two passwords don't match, return a 401 status.
return echo.NewHTTPError(http.StatusUnauthorized, "Incorrect login credentials, please try again")
}
var expireAt time.Time
// Set cookie expiration to 100 years to make it persistent.
cookieExp := time.Now().AddDate(100, 0, 0)
if !signin.Remember {
expireAt = time.Now().Add(auth.AccessTokenDuration)
cookieExp = time.Now().Add(auth.CookieExpDuration)
}
accessToken, err := auth.GenerateAccessToken(user.Username, user.ID, expireAt, []byte(s.Secret))
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to generate tokens, err: %s", err)).SetInternal(err)
}
if err := s.UpsertAccessTokenToStore(ctx, user, accessToken); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to upsert access token, err: %s", err)).SetInternal(err)
}
setTokenCookie(c, auth.AccessTokenCookieName, accessToken, cookieExp)
userMessage := convertUserFromStore(user)
return c.JSON(http.StatusOK, userMessage)
}
// SignInSSO godoc
//
// @Summary Sign-in to memos using SSO.
// @Tags auth
// @Accept json
// @Produce json
// @Param body body SSOSignIn true "SSO sign-in object"
// @Success 200 {object} store.User "User information"
// @Failure 400 {object} nil "Malformatted signin request"
// @Failure 401 {object} nil "Access denied, identifier does not match the filter."
// @Failure 403 {object} nil "User has been archived with username {username}"
// @Failure 404 {object} nil "Identity provider not found"
// @Failure 500 {object} nil "Failed to find identity provider | Failed to create identity provider instance | Failed to exchange token | Failed to get user info | Failed to compile identifier filter | Incorrect login credentials, please try again | Failed to generate random password | Failed to generate password hash | Failed to create user | Failed to generate tokens | Failed to create activity"
// @Router /api/v1/auth/signin/sso [POST]
func (s *APIV1Service) SignInSSO(c echo.Context) error {
ctx := c.Request().Context()
signin := &SSOSignIn{}
if err := json.NewDecoder(c.Request().Body).Decode(signin); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted signin request").SetInternal(err)
}
identityProvider, err := s.Store.GetIdentityProvider(ctx, &store.FindIdentityProvider{
ID: &signin.IdentityProviderID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find identity provider").SetInternal(err)
}
if identityProvider == nil {
return echo.NewHTTPError(http.StatusNotFound, "Identity provider not found")
}
var userInfo *idp.IdentityProviderUserInfo
if identityProvider.Type == store.IdentityProviderOAuth2Type {
oauth2IdentityProvider, err := oauth2.NewIdentityProvider(identityProvider.Config.OAuth2Config)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create identity provider instance").SetInternal(err)
}
token, err := oauth2IdentityProvider.ExchangeToken(ctx, signin.RedirectURI, signin.Code)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to exchange token").SetInternal(err)
}
userInfo, err = oauth2IdentityProvider.UserInfo(token)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get user info").SetInternal(err)
}
}
identifierFilter := identityProvider.IdentifierFilter
if identifierFilter != "" {
identifierFilterRegex, err := regexp.Compile(identifierFilter)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compile identifier filter").SetInternal(err)
}
if !identifierFilterRegex.MatchString(userInfo.Identifier) {
return echo.NewHTTPError(http.StatusUnauthorized, "Access denied, identifier does not match the filter.").SetInternal(err)
}
}
user, err := s.Store.GetUser(ctx, &store.FindUser{
Username: &userInfo.Identifier,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Incorrect login credentials, please try again")
}
if user == nil {
allowSignUpSetting, err := s.Store.GetSystemSetting(ctx, &store.FindSystemSetting{
Name: SystemSettingAllowSignUpName.String(),
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find system setting").SetInternal(err)
}
allowSignUpSettingValue := true
if allowSignUpSetting != nil {
err = json.Unmarshal([]byte(allowSignUpSetting.Value), &allowSignUpSettingValue)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal system setting allow signup").SetInternal(err)
}
}
if !allowSignUpSettingValue {
return echo.NewHTTPError(http.StatusUnauthorized, "signup is disabled").SetInternal(err)
}
userCreate := &store.User{
Username: userInfo.Identifier,
// The new signup user should be normal user by default.
Role: store.RoleUser,
Nickname: userInfo.DisplayName,
Email: userInfo.Email,
}
password, err := util.RandomString(20)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate random password").SetInternal(err)
}
passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
}
userCreate.PasswordHash = string(passwordHash)
user, err = s.Store.CreateUser(ctx, userCreate)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create user").SetInternal(err)
}
}
if user.RowStatus == store.Archived {
return echo.NewHTTPError(http.StatusForbidden, fmt.Sprintf("User has been archived with username %s", userInfo.Identifier))
}
accessToken, err := auth.GenerateAccessToken(user.Username, user.ID, time.Now().Add(auth.AccessTokenDuration), []byte(s.Secret))
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to generate tokens, err: %s", err)).SetInternal(err)
}
if err := s.UpsertAccessTokenToStore(ctx, user, accessToken); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to upsert access token, err: %s", err)).SetInternal(err)
}
cookieExp := time.Now().Add(auth.CookieExpDuration)
setTokenCookie(c, auth.AccessTokenCookieName, accessToken, cookieExp)
userMessage := convertUserFromStore(user)
return c.JSON(http.StatusOK, userMessage)
}
// SignOut godoc
//
// @Summary Sign-out from memos.
// @Tags auth
// @Produce json
// @Success 200 {boolean} true "Sign-out success"
// @Router /api/v1/auth/signout [POST]
func (s *APIV1Service) SignOut(c echo.Context) error {
accessToken := findAccessToken(c)
userID, _ := getUserIDFromAccessToken(accessToken, s.Secret)
err := removeAccessTokenAndCookies(c, s.Store, userID, accessToken)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to remove access token, err: %s", err)).SetInternal(err)
}
return c.JSON(http.StatusOK, true)
}
// SignUp godoc
//
// @Summary Sign-up to memos.
// @Tags auth
// @Accept json
// @Produce json
// @Param body body SignUp true "Sign-up object"
// @Success 200 {object} store.User "User information"
// @Failure 400 {object} nil "Malformatted signup request | Failed to find users"
// @Failure 401 {object} nil "signup is disabled"
// @Failure 403 {object} nil "Forbidden"
// @Failure 404 {object} nil "Not found"
// @Failure 500 {object} nil "Failed to find system setting | Failed to unmarshal system setting allow signup | Failed to generate password hash | Failed to create user | Failed to generate tokens | Failed to create activity"
// @Router /api/v1/auth/signup [POST]
func (s *APIV1Service) SignUp(c echo.Context) error {
ctx := c.Request().Context()
signup := &SignUp{}
if err := json.NewDecoder(c.Request().Body).Decode(signup); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted signup request").SetInternal(err)
}
hostUserType := store.RoleHost
existedHostUsers, err := s.Store.ListUsers(ctx, &store.FindUser{
Role: &hostUserType,
})
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Failed to find users").SetInternal(err)
}
if !usernameMatcher.MatchString(strings.ToLower(signup.Username)) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid username %s", signup.Username)).SetInternal(err)
}
userCreate := &store.User{
Username: signup.Username,
// The new signup user should be normal user by default.
Role: store.RoleUser,
Nickname: signup.Username,
}
if len(existedHostUsers) == 0 {
// Change the default role to host if there is no host user.
userCreate.Role = store.RoleHost
} else {
allowSignUpSetting, err := s.Store.GetSystemSetting(ctx, &store.FindSystemSetting{
Name: SystemSettingAllowSignUpName.String(),
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find system setting").SetInternal(err)
}
allowSignUpSettingValue := true
if allowSignUpSetting != nil {
err = json.Unmarshal([]byte(allowSignUpSetting.Value), &allowSignUpSettingValue)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal system setting allow signup").SetInternal(err)
}
}
if !allowSignUpSettingValue {
return echo.NewHTTPError(http.StatusUnauthorized, "signup is disabled").SetInternal(err)
}
}
passwordHash, err := bcrypt.GenerateFromPassword([]byte(signup.Password), bcrypt.DefaultCost)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
}
userCreate.PasswordHash = string(passwordHash)
user, err := s.Store.CreateUser(ctx, userCreate)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create user").SetInternal(err)
}
accessToken, err := auth.GenerateAccessToken(user.Username, user.ID, time.Now().Add(auth.AccessTokenDuration), []byte(s.Secret))
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to generate tokens, err: %s", err)).SetInternal(err)
}
if err := s.UpsertAccessTokenToStore(ctx, user, accessToken); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to upsert access token, err: %s", err)).SetInternal(err)
}
cookieExp := time.Now().Add(auth.CookieExpDuration)
setTokenCookie(c, auth.AccessTokenCookieName, accessToken, cookieExp)
userMessage := convertUserFromStore(user)
return c.JSON(http.StatusOK, userMessage)
}
func (s *APIV1Service) UpsertAccessTokenToStore(ctx context.Context, user *store.User, accessToken string) error {
userAccessTokens, err := s.Store.GetUserAccessTokens(ctx, user.ID)
if err != nil {
return errors.Wrap(err, "failed to get user access tokens")
}
userAccessToken := storepb.AccessTokensUserSetting_AccessToken{
AccessToken: accessToken,
Description: "Account sign in",
}
userAccessTokens = append(userAccessTokens, &userAccessToken)
if _, err := s.Store.UpsertUserSettingV1(ctx, &storepb.UserSetting{
UserId: user.ID,
Key: storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS,
Value: &storepb.UserSetting_AccessTokens{
AccessTokens: &storepb.AccessTokensUserSetting{
AccessTokens: userAccessTokens,
},
},
}); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to upsert user setting, err: %s", err)).SetInternal(err)
}
return nil
}
// removeAccessTokenAndCookies removes the jwt token from the cookies.
func removeAccessTokenAndCookies(c echo.Context, s *store.Store, userID int32, token string) error {
err := s.RemoveUserAccessToken(c.Request().Context(), userID, token)
if err != nil {
return err
}
cookieExp := time.Now().Add(-1 * time.Hour)
setTokenCookie(c, auth.AccessTokenCookieName, "", cookieExp)
return nil
}
// setTokenCookie sets the token to the cookie.
func setTokenCookie(c echo.Context, name, token string, expiration time.Time) {
cookie := new(http.Cookie)
cookie.Name = name
cookie.Value = token
cookie.Expires = expiration
cookie.Path = "/"
// Http-only helps mitigate the risk of client side script accessing the protected cookie.
cookie.HttpOnly = true
cookie.SameSite = http.SameSiteStrictMode
c.SetCookie(cookie)
}
-15
View File
@@ -1,15 +0,0 @@
package v1
// RowStatus is the status for a row.
type RowStatus string
const (
// Normal is the status for a normal row.
Normal RowStatus = "NORMAL"
// Archived is the status for an archived row.
Archived RowStatus = "ARCHIVED"
)
func (r RowStatus) String() string {
return string(r)
}
-3015
View File
File diff suppressed because it is too large Load Diff
-49
View File
@@ -1,49 +0,0 @@
package v1
import (
"fmt"
"net/http"
"net/url"
"github.com/labstack/echo/v4"
getter "github.com/usememos/memos/plugin/http-getter"
)
func (*APIV1Service) registerGetterPublicRoutes(g *echo.Group) {
// GET /get/image?url={url} - Get image.
g.GET("/get/image", GetImage)
}
// GetImage godoc
//
// @Summary Get GetImage from URL
// @Tags image-url
// @Produce GetImage/*
// @Param url query string true "Image url"
// @Success 200 {object} nil "Image"
// @Failure 400 {object} nil "Missing GetImage url | Wrong url | Failed to get GetImage url: %s"
// @Failure 500 {object} nil "Failed to write GetImage blob"
// @Router /o/get/GetImage [GET]
func GetImage(c echo.Context) error {
urlStr := c.QueryParam("url")
if urlStr == "" {
return echo.NewHTTPError(http.StatusBadRequest, "Missing image url")
}
if _, err := url.Parse(urlStr); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Wrong url").SetInternal(err)
}
image, err := getter.GetImage(urlStr)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Failed to get image url: %s", urlStr)).SetInternal(err)
}
c.Response().Writer.WriteHeader(http.StatusOK)
c.Response().Writer.Header().Set("Content-Type", image.Mediatype)
c.Response().Writer.Header().Set(echo.HeaderCacheControl, "max-age=31536000, immutable")
if _, err := c.Response().Writer.Write(image.Blob); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to write image blob").SetInternal(err)
}
return nil
}
-349
View File
@@ -1,349 +0,0 @@
package v1
import (
"encoding/json"
"fmt"
"net/http"
"github.com/labstack/echo/v4"
"github.com/usememos/memos/internal/util"
"github.com/usememos/memos/store"
)
type IdentityProviderType string
const (
IdentityProviderOAuth2Type IdentityProviderType = "OAUTH2"
)
func (t IdentityProviderType) String() string {
return string(t)
}
type IdentityProviderConfig struct {
OAuth2Config *IdentityProviderOAuth2Config `json:"oauth2Config"`
}
type IdentityProviderOAuth2Config struct {
ClientID string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
AuthURL string `json:"authUrl"`
TokenURL string `json:"tokenUrl"`
UserInfoURL string `json:"userInfoUrl"`
Scopes []string `json:"scopes"`
FieldMapping *FieldMapping `json:"fieldMapping"`
}
type FieldMapping struct {
Identifier string `json:"identifier"`
DisplayName string `json:"displayName"`
Email string `json:"email"`
}
type IdentityProvider struct {
ID int32 `json:"id"`
Name string `json:"name"`
Type IdentityProviderType `json:"type"`
IdentifierFilter string `json:"identifierFilter"`
Config *IdentityProviderConfig `json:"config"`
}
type CreateIdentityProviderRequest struct {
Name string `json:"name"`
Type IdentityProviderType `json:"type"`
IdentifierFilter string `json:"identifierFilter"`
Config *IdentityProviderConfig `json:"config"`
}
type UpdateIdentityProviderRequest struct {
ID int32 `json:"-"`
Type IdentityProviderType `json:"type"`
Name *string `json:"name"`
IdentifierFilter *string `json:"identifierFilter"`
Config *IdentityProviderConfig `json:"config"`
}
func (s *APIV1Service) registerIdentityProviderRoutes(g *echo.Group) {
g.GET("/idp", s.GetIdentityProviderList)
g.POST("/idp", s.CreateIdentityProvider)
g.GET("/idp/:idpId", s.GetIdentityProvider)
g.PATCH("/idp/:idpId", s.UpdateIdentityProvider)
g.DELETE("/idp/:idpId", s.DeleteIdentityProvider)
}
// GetIdentityProviderList godoc
//
// @Summary Get a list of identity providers
// @Description *clientSecret is only available for host user
// @Tags idp
// @Produce json
// @Success 200 {object} []IdentityProvider "List of available identity providers"
// @Failure 500 {object} nil "Failed to find identity provider list | Failed to find user"
// @Router /api/v1/idp [GET]
func (s *APIV1Service) GetIdentityProviderList(c echo.Context) error {
ctx := c.Request().Context()
list, err := s.Store.ListIdentityProviders(ctx, &store.FindIdentityProvider{})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find identity provider list").SetInternal(err)
}
userID, ok := c.Get(userIDContextKey).(int32)
isHostUser := false
if ok {
user, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if user == nil || user.Role == store.RoleHost {
isHostUser = true
}
}
identityProviderList := []*IdentityProvider{}
for _, item := range list {
identityProvider := convertIdentityProviderFromStore(item)
// data desensitize
if !isHostUser {
identityProvider.Config.OAuth2Config.ClientSecret = ""
}
identityProviderList = append(identityProviderList, identityProvider)
}
return c.JSON(http.StatusOK, identityProviderList)
}
// CreateIdentityProvider godoc
//
// @Summary Create Identity Provider
// @Tags idp
// @Accept json
// @Produce json
// @Param body body CreateIdentityProviderRequest true "Identity provider information"
// @Success 200 {object} store.IdentityProvider "Identity provider information"
// @Failure 401 {object} nil "Missing user in session | Unauthorized"
// @Failure 400 {object} nil "Malformatted post identity provider request"
// @Failure 500 {object} nil "Failed to find user | Failed to create identity provider"
// @Router /api/v1/idp [POST]
func (s *APIV1Service) CreateIdentityProvider(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
user, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if user == nil || user.Role != store.RoleHost {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
identityProviderCreate := &CreateIdentityProviderRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(identityProviderCreate); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post identity provider request").SetInternal(err)
}
identityProvider, err := s.Store.CreateIdentityProvider(ctx, &store.IdentityProvider{
Name: identityProviderCreate.Name,
Type: store.IdentityProviderType(identityProviderCreate.Type),
IdentifierFilter: identityProviderCreate.IdentifierFilter,
Config: convertIdentityProviderConfigToStore(identityProviderCreate.Config),
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create identity provider").SetInternal(err)
}
return c.JSON(http.StatusOK, convertIdentityProviderFromStore(identityProvider))
}
// GetIdentityProvider godoc
//
// @Summary Get an identity provider by ID
// @Tags idp
// @Accept json
// @Produce json
// @Param idpId path int true "Identity provider ID"
// @Success 200 {object} store.IdentityProvider "Requested identity provider"
// @Failure 400 {object} nil "ID is not a number: %s"
// @Failure 401 {object} nil "Missing user in session | Unauthorized"
// @Failure 404 {object} nil "Identity provider not found"
// @Failure 500 {object} nil "Failed to find identity provider list | Failed to find user"
// @Router /api/v1/idp/{idpId} [GET]
func (s *APIV1Service) GetIdentityProvider(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
user, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if user == nil || user.Role != store.RoleHost {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
identityProviderID, err := util.ConvertStringToInt32(c.Param("idpId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("idpId"))).SetInternal(err)
}
identityProvider, err := s.Store.GetIdentityProvider(ctx, &store.FindIdentityProvider{
ID: &identityProviderID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get identity provider").SetInternal(err)
}
if identityProvider == nil {
return echo.NewHTTPError(http.StatusNotFound, "Identity provider not found")
}
return c.JSON(http.StatusOK, convertIdentityProviderFromStore(identityProvider))
}
// DeleteIdentityProvider godoc
//
// @Summary Delete an identity provider by ID
// @Tags idp
// @Accept json
// @Produce json
// @Param idpId path int true "Identity Provider ID"
// @Success 200 {boolean} true "Identity Provider deleted"
// @Failure 400 {object} nil "ID is not a number: %s | Malformatted patch identity provider request"
// @Failure 401 {object} nil "Missing user in session | Unauthorized"
// @Failure 500 {object} nil "Failed to find user | Failed to patch identity provider"
// @Router /api/v1/idp/{idpId} [DELETE]
func (s *APIV1Service) DeleteIdentityProvider(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
user, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if user == nil || user.Role != store.RoleHost {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
identityProviderID, err := util.ConvertStringToInt32(c.Param("idpId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("idpId"))).SetInternal(err)
}
if err = s.Store.DeleteIdentityProvider(ctx, &store.DeleteIdentityProvider{ID: identityProviderID}); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete identity provider").SetInternal(err)
}
return c.JSON(http.StatusOK, true)
}
// UpdateIdentityProvider godoc
//
// @Summary Update an identity provider by ID
// @Tags idp
// @Accept json
// @Produce json
// @Param idpId path int true "Identity Provider ID"
// @Param body body UpdateIdentityProviderRequest true "Patched identity provider information"
// @Success 200 {object} store.IdentityProvider "Patched identity provider"
// @Failure 400 {object} nil "ID is not a number: %s | Malformatted patch identity provider request"
// @Failure 401 {object} nil "Missing user in session | Unauthorized
// @Failure 500 {object} nil "Failed to find user | Failed to patch identity provider"
// @Router /api/v1/idp/{idpId} [PATCH]
func (s *APIV1Service) UpdateIdentityProvider(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
user, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if user == nil || user.Role != store.RoleHost {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
identityProviderID, err := util.ConvertStringToInt32(c.Param("idpId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("idpId"))).SetInternal(err)
}
identityProviderPatch := &UpdateIdentityProviderRequest{
ID: identityProviderID,
}
if err := json.NewDecoder(c.Request().Body).Decode(identityProviderPatch); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch identity provider request").SetInternal(err)
}
identityProvider, err := s.Store.UpdateIdentityProvider(ctx, &store.UpdateIdentityProvider{
ID: identityProviderPatch.ID,
Type: store.IdentityProviderType(identityProviderPatch.Type),
Name: identityProviderPatch.Name,
IdentifierFilter: identityProviderPatch.IdentifierFilter,
Config: convertIdentityProviderConfigToStore(identityProviderPatch.Config),
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch identity provider").SetInternal(err)
}
return c.JSON(http.StatusOK, convertIdentityProviderFromStore(identityProvider))
}
func convertIdentityProviderFromStore(identityProvider *store.IdentityProvider) *IdentityProvider {
return &IdentityProvider{
ID: identityProvider.ID,
Name: identityProvider.Name,
Type: IdentityProviderType(identityProvider.Type),
IdentifierFilter: identityProvider.IdentifierFilter,
Config: convertIdentityProviderConfigFromStore(identityProvider.Config),
}
}
func convertIdentityProviderConfigFromStore(config *store.IdentityProviderConfig) *IdentityProviderConfig {
return &IdentityProviderConfig{
OAuth2Config: &IdentityProviderOAuth2Config{
ClientID: config.OAuth2Config.ClientID,
ClientSecret: config.OAuth2Config.ClientSecret,
AuthURL: config.OAuth2Config.AuthURL,
TokenURL: config.OAuth2Config.TokenURL,
UserInfoURL: config.OAuth2Config.UserInfoURL,
Scopes: config.OAuth2Config.Scopes,
FieldMapping: &FieldMapping{
Identifier: config.OAuth2Config.FieldMapping.Identifier,
DisplayName: config.OAuth2Config.FieldMapping.DisplayName,
Email: config.OAuth2Config.FieldMapping.Email,
},
},
}
}
func convertIdentityProviderConfigToStore(config *IdentityProviderConfig) *store.IdentityProviderConfig {
return &store.IdentityProviderConfig{
OAuth2Config: &store.IdentityProviderOAuth2Config{
ClientID: config.OAuth2Config.ClientID,
ClientSecret: config.OAuth2Config.ClientSecret,
AuthURL: config.OAuth2Config.AuthURL,
TokenURL: config.OAuth2Config.TokenURL,
UserInfoURL: config.OAuth2Config.UserInfoURL,
Scopes: config.OAuth2Config.Scopes,
FieldMapping: &store.FieldMapping{
Identifier: config.OAuth2Config.FieldMapping.Identifier,
DisplayName: config.OAuth2Config.FieldMapping.DisplayName,
Email: config.OAuth2Config.FieldMapping.Email,
},
},
}
}
-156
View File
@@ -1,156 +0,0 @@
package v1
import (
"fmt"
"net/http"
"strings"
"github.com/golang-jwt/jwt/v4"
"github.com/labstack/echo/v4"
"github.com/pkg/errors"
"go.uber.org/zap"
"github.com/usememos/memos/api/auth"
"github.com/usememos/memos/internal/log"
"github.com/usememos/memos/internal/util"
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
)
const (
// The key name used to store user id in the context
// user id is extracted from the jwt token subject field.
userIDContextKey = "user-id"
)
func extractTokenFromHeader(c echo.Context) (string, error) {
authHeader := c.Request().Header.Get("Authorization")
if authHeader == "" {
return "", nil
}
authHeaderParts := strings.Fields(authHeader)
if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" {
return "", errors.New("Authorization header format must be Bearer {token}")
}
return authHeaderParts[1], nil
}
func findAccessToken(c echo.Context) string {
// Check the HTTP request header first.
accessToken, _ := extractTokenFromHeader(c)
if accessToken == "" {
// Check the cookie.
cookie, _ := c.Cookie(auth.AccessTokenCookieName)
if cookie != nil {
accessToken = cookie.Value
}
}
return accessToken
}
// JWTMiddleware validates the access token.
func JWTMiddleware(server *APIV1Service, next echo.HandlerFunc, secret string) echo.HandlerFunc {
return func(c echo.Context) error {
ctx := c.Request().Context()
path := c.Request().URL.Path
method := c.Request().Method
if server.defaultAuthSkipper(c) {
return next(c)
}
// Skip validation for server status endpoints.
if util.HasPrefixes(path, "/api/v1/ping", "/api/v1/status") && method == http.MethodGet {
return next(c)
}
accessToken := findAccessToken(c)
if accessToken == "" {
// Allow the user to access the public endpoints.
if util.HasPrefixes(path, "/o") {
return next(c)
}
// When the request is not authenticated, we allow the user to access the memo endpoints for those public memos.
if util.HasPrefixes(path, "/api/v1/idp", "/api/v1/memo", "/api/v1/user") && path != "/api/v1/user" && method == http.MethodGet {
return next(c)
}
return echo.NewHTTPError(http.StatusUnauthorized, "Missing access token")
}
userID, err := getUserIDFromAccessToken(accessToken, secret)
if err != nil {
err = removeAccessTokenAndCookies(c, server.Store, userID, accessToken)
if err != nil {
log.Error("fail to remove AccessToken and Cookies", zap.Error(err))
}
return echo.NewHTTPError(http.StatusUnauthorized, "Invalid or expired access token")
}
accessTokens, err := server.Store.GetUserAccessTokens(ctx, userID)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get user access tokens.").WithInternal(err)
}
if !validateAccessToken(accessToken, accessTokens) {
err = removeAccessTokenAndCookies(c, server.Store, userID, accessToken)
if err != nil {
log.Error("fail to remove AccessToken and Cookies", zap.Error(err))
}
return echo.NewHTTPError(http.StatusUnauthorized, "Invalid access token.")
}
// Even if there is no error, we still need to make sure the user still exists.
user, err := server.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to find user ID: %d", userID)).SetInternal(err)
}
if user == nil {
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Failed to find user ID: %d", userID))
}
// Stores userID into context.
c.Set(userIDContextKey, userID)
return next(c)
}
}
func getUserIDFromAccessToken(accessToken, secret string) (int32, error) {
claims := &auth.ClaimsMessage{}
_, err := jwt.ParseWithClaims(accessToken, claims, func(t *jwt.Token) (any, error) {
if t.Method.Alg() != jwt.SigningMethodHS256.Name {
return nil, errors.Errorf("unexpected access token signing method=%v, expect %v", t.Header["alg"], jwt.SigningMethodHS256)
}
if kid, ok := t.Header["kid"].(string); ok {
if kid == "v1" {
return []byte(secret), nil
}
}
return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"])
})
if err != nil {
return 0, errors.Wrap(err, "Invalid or expired access token")
}
// We either have a valid access token or we will attempt to generate new access token.
userID, err := util.ConvertStringToInt32(claims.Subject)
if err != nil {
return 0, errors.Wrap(err, "Malformed ID in the token")
}
return userID, nil
}
func (*APIV1Service) defaultAuthSkipper(c echo.Context) bool {
path := c.Path()
return util.HasPrefixes(path, "/api/v1/auth")
}
func validateAccessToken(accessTokenString string, userAccessTokens []*storepb.AccessTokensUserSetting_AccessToken) bool {
for _, userAccessToken := range userAccessTokens {
if accessTokenString == userAccessToken.AccessToken {
return true
}
}
return false
}
-1047
View File
File diff suppressed because it is too large Load Diff
-97
View File
@@ -1,97 +0,0 @@
package v1
import (
"encoding/json"
"fmt"
"net/http"
"github.com/labstack/echo/v4"
"github.com/usememos/memos/internal/util"
"github.com/usememos/memos/store"
)
type MemoOrganizer struct {
MemoID int32 `json:"memoId"`
UserID int32 `json:"userId"`
Pinned bool `json:"pinned"`
}
type UpsertMemoOrganizerRequest struct {
Pinned bool `json:"pinned"`
}
func (s *APIV1Service) registerMemoOrganizerRoutes(g *echo.Group) {
g.POST("/memo/:memoId/organizer", s.CreateMemoOrganizer)
}
// CreateMemoOrganizer godoc
//
// @Summary Organize memo (pin/unpin)
// @Tags memo-organizer
// @Accept json
// @Produce json
// @Param memoId path int true "ID of memo to organize"
// @Param body body UpsertMemoOrganizerRequest true "Memo organizer object"
// @Success 200 {object} store.Memo "Memo information"
// @Failure 400 {object} nil "ID is not a number: %s | Malformatted post memo organizer request"
// @Failure 401 {object} nil "Missing user in session | Unauthorized"
// @Failure 404 {object} nil "Memo not found: %v"
// @Failure 500 {object} nil "Failed to find memo | Failed to upsert memo organizer | Failed to find memo by ID: %v | Failed to compose memo response"
// @Router /api/v1/memo/{memoId}/organizer [POST]
func (s *APIV1Service) CreateMemoOrganizer(c echo.Context) error {
ctx := c.Request().Context()
memoID, err := util.ConvertStringToInt32(c.Param("memoId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
}
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
memo, err := s.Store.GetMemo(ctx, &store.FindMemo{
ID: &memoID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo").SetInternal(err)
}
if memo == nil {
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Memo not found: %v", memoID))
}
if memo.CreatorID != userID {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
request := &UpsertMemoOrganizerRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post memo organizer request").SetInternal(err)
}
upsert := &store.MemoOrganizer{
MemoID: memoID,
UserID: userID,
Pinned: request.Pinned,
}
_, err = s.Store.UpsertMemoOrganizer(ctx, upsert)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo organizer").SetInternal(err)
}
memo, err = s.Store.GetMemo(ctx, &store.FindMemo{
ID: &memoID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find memo by ID: %v", memoID)).SetInternal(err)
}
if memo == nil {
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Memo not found: %v", memoID))
}
memoResponse, err := s.convertMemoFromStore(ctx, memo)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to compose memo response").SetInternal(err)
}
return c.JSON(http.StatusOK, memoResponse)
}
-156
View File
@@ -1,156 +0,0 @@
package v1
import (
"encoding/json"
"fmt"
"net/http"
"github.com/labstack/echo/v4"
"github.com/usememos/memos/internal/util"
"github.com/usememos/memos/store"
)
type MemoRelationType string
const (
MemoRelationReference MemoRelationType = "REFERENCE"
MemoRelationComment MemoRelationType = "COMMENT"
)
func (t MemoRelationType) String() string {
return string(t)
}
type MemoRelation struct {
MemoID int32 `json:"memoId"`
RelatedMemoID int32 `json:"relatedMemoId"`
Type MemoRelationType `json:"type"`
}
type UpsertMemoRelationRequest struct {
RelatedMemoID int32 `json:"relatedMemoId"`
Type MemoRelationType `json:"type"`
}
func (s *APIV1Service) registerMemoRelationRoutes(g *echo.Group) {
g.GET("/memo/:memoId/relation", s.GetMemoRelationList)
g.POST("/memo/:memoId/relation", s.CreateMemoRelation)
g.DELETE("/memo/:memoId/relation/:relatedMemoId/type/:relationType", s.DeleteMemoRelation)
}
// GetMemoRelationList godoc
//
// @Summary Get a list of Memo Relations
// @Tags memo-relation
// @Accept json
// @Produce json
// @Param memoId path int true "ID of memo to find relations"
// @Success 200 {object} []store.MemoRelation "Memo relation information list"
// @Failure 400 {object} nil "ID is not a number: %s"
// @Failure 500 {object} nil "Failed to list memo relations"
// @Router /api/v1/memo/{memoId}/relation [GET]
func (s *APIV1Service) GetMemoRelationList(c echo.Context) error {
ctx := c.Request().Context()
memoID, err := util.ConvertStringToInt32(c.Param("memoId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
}
memoRelationList, err := s.Store.ListMemoRelations(ctx, &store.FindMemoRelation{
MemoID: &memoID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to list memo relations").SetInternal(err)
}
return c.JSON(http.StatusOK, memoRelationList)
}
// CreateMemoRelation godoc
//
// @Summary Create Memo Relation
// @Description Create a relation between two memos
// @Tags memo-relation
// @Accept json
// @Produce json
// @Param memoId path int true "ID of memo to relate"
// @Param body body UpsertMemoRelationRequest true "Memo relation object"
// @Success 200 {object} store.MemoRelation "Memo relation information"
// @Failure 400 {object} nil "ID is not a number: %s | Malformatted post memo relation request"
// @Failure 500 {object} nil "Failed to upsert memo relation"
// @Router /api/v1/memo/{memoId}/relation [POST]
//
// NOTES:
// - Currently not secured
// - It's possible to create relations to memos that doesn't exist, which will trigger 404 errors when the frontend tries to load them.
// - It's possible to create multiple relations, though the interface only shows first.
func (s *APIV1Service) CreateMemoRelation(c echo.Context) error {
ctx := c.Request().Context()
memoID, err := util.ConvertStringToInt32(c.Param("memoId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
}
request := &UpsertMemoRelationRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post memo relation request").SetInternal(err)
}
memoRelation, err := s.Store.UpsertMemoRelation(ctx, &store.MemoRelation{
MemoID: memoID,
RelatedMemoID: request.RelatedMemoID,
Type: store.MemoRelationType(request.Type),
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert memo relation").SetInternal(err)
}
return c.JSON(http.StatusOK, memoRelation)
}
// DeleteMemoRelation godoc
//
// @Summary Delete a Memo Relation
// @Description Removes a relation between two memos
// @Tags memo-relation
// @Accept json
// @Produce json
// @Param memoId path int true "ID of memo to find relations"
// @Param relatedMemoId path int true "ID of memo to remove relation to"
// @Param relationType path MemoRelationType true "Type of relation to remove"
// @Success 200 {boolean} true "Memo relation deleted"
// @Failure 400 {object} nil "Memo ID is not a number: %s | Related memo ID is not a number: %s"
// @Failure 500 {object} nil "Failed to delete memo relation"
// @Router /api/v1/memo/{memoId}/relation/{relatedMemoId}/type/{relationType} [DELETE]
//
// NOTES:
// - Currently not secured.
// - Will always return true, even if the relation doesn't exist.
func (s *APIV1Service) DeleteMemoRelation(c echo.Context) error {
ctx := c.Request().Context()
memoID, err := util.ConvertStringToInt32(c.Param("memoId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Memo ID is not a number: %s", c.Param("memoId"))).SetInternal(err)
}
relatedMemoID, err := util.ConvertStringToInt32(c.Param("relatedMemoId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Related memo ID is not a number: %s", c.Param("relatedMemoId"))).SetInternal(err)
}
relationType := store.MemoRelationType(c.Param("relationType"))
if err := s.Store.DeleteMemoRelation(ctx, &store.DeleteMemoRelation{
MemoID: &memoID,
RelatedMemoID: &relatedMemoID,
Type: &relationType,
}); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete memo relation").SetInternal(err)
}
return c.JSON(http.StatusOK, true)
}
func convertMemoRelationFromStore(memoRelation *store.MemoRelation) *MemoRelation {
return &MemoRelation{
MemoID: memoRelation.MemoID,
RelatedMemoID: memoRelation.RelatedMemoID,
Type: MemoRelationType(memoRelation.Type),
}
}
-487
View File
@@ -1,487 +0,0 @@
package v1
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"github.com/labstack/echo/v4"
"github.com/pkg/errors"
"go.uber.org/zap"
"github.com/usememos/memos/internal/log"
"github.com/usememos/memos/internal/util"
"github.com/usememos/memos/plugin/storage/s3"
"github.com/usememos/memos/server/service/metric"
"github.com/usememos/memos/store"
)
type Resource struct {
ID int32 `json:"id"`
// Standard fields
CreatorID int32 `json:"creatorId"`
CreatedTs int64 `json:"createdTs"`
UpdatedTs int64 `json:"updatedTs"`
// Domain specific fields
Filename string `json:"filename"`
Blob []byte `json:"-"`
InternalPath string `json:"-"`
ExternalLink string `json:"externalLink"`
Type string `json:"type"`
Size int64 `json:"size"`
}
type CreateResourceRequest struct {
Filename string `json:"filename"`
ExternalLink string `json:"externalLink"`
Type string `json:"type"`
}
type FindResourceRequest struct {
ID *int32 `json:"id"`
CreatorID *int32 `json:"creatorId"`
Filename *string `json:"filename"`
}
type UpdateResourceRequest struct {
Filename *string `json:"filename"`
}
const (
// The upload memory buffer is 32 MiB.
// It should be kept low, so RAM usage doesn't get out of control.
// This is unrelated to maximum upload size limit, which is now set through system setting.
maxUploadBufferSizeBytes = 32 << 20
MebiByte = 1024 * 1024
)
var fileKeyPattern = regexp.MustCompile(`\{[a-z]{1,9}\}`)
func (s *APIV1Service) registerResourceRoutes(g *echo.Group) {
g.GET("/resource", s.GetResourceList)
g.POST("/resource", s.CreateResource)
g.POST("/resource/blob", s.UploadResource)
g.PATCH("/resource/:resourceId", s.UpdateResource)
g.DELETE("/resource/:resourceId", s.DeleteResource)
}
// GetResourceList godoc
//
// @Summary Get a list of resources
// @Tags resource
// @Produce json
// @Param limit query int false "Limit"
// @Param offset query int false "Offset"
// @Success 200 {object} []store.Resource "Resource list"
// @Failure 401 {object} nil "Missing user in session"
// @Failure 500 {object} nil "Failed to fetch resource list"
// @Router /api/v1/resource [GET]
func (s *APIV1Service) GetResourceList(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
find := &store.FindResource{
CreatorID: &userID,
}
if limit, err := strconv.Atoi(c.QueryParam("limit")); err == nil {
find.Limit = &limit
}
if offset, err := strconv.Atoi(c.QueryParam("offset")); err == nil {
find.Offset = &offset
}
list, err := s.Store.ListResources(ctx, find)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch resource list").SetInternal(err)
}
resourceMessageList := []*Resource{}
for _, resource := range list {
resourceMessageList = append(resourceMessageList, convertResourceFromStore(resource))
}
return c.JSON(http.StatusOK, resourceMessageList)
}
// CreateResource godoc
//
// @Summary Create resource
// @Tags resource
// @Accept json
// @Produce json
// @Param body body CreateResourceRequest true "Request object."
// @Success 200 {object} store.Resource "Created resource"
// @Failure 400 {object} nil "Malformatted post resource request | Invalid external link | Invalid external link scheme | Failed to request %s | Failed to read %s | Failed to read mime from %s"
// @Failure 401 {object} nil "Missing user in session"
// @Failure 500 {object} nil "Failed to save resource | Failed to create resource | Failed to create activity"
// @Router /api/v1/resource [POST]
func (s *APIV1Service) CreateResource(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
request := &CreateResourceRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post resource request").SetInternal(err)
}
create := &store.Resource{
CreatorID: userID,
Filename: request.Filename,
ExternalLink: request.ExternalLink,
Type: request.Type,
}
if request.ExternalLink != "" {
// Only allow those external links scheme with http/https
linkURL, err := url.Parse(request.ExternalLink)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid external link").SetInternal(err)
}
if linkURL.Scheme != "http" && linkURL.Scheme != "https" {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid external link scheme")
}
}
resource, err := s.Store.CreateResource(ctx, create)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create resource").SetInternal(err)
}
metric.Enqueue("resource create")
return c.JSON(http.StatusOK, convertResourceFromStore(resource))
}
// UploadResource godoc
//
// @Summary Upload resource
// @Tags resource
// @Accept multipart/form-data
// @Produce json
// @Param file formData file true "File to upload"
// @Success 200 {object} store.Resource "Created resource"
// @Failure 400 {object} nil "Upload file not found | File size exceeds allowed limit of %d MiB | Failed to parse upload data"
// @Failure 401 {object} nil "Missing user in session"
// @Failure 500 {object} nil "Failed to get uploading file | Failed to open file | Failed to save resource | Failed to create resource | Failed to create activity"
// @Router /api/v1/resource/blob [POST]
func (s *APIV1Service) UploadResource(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
// This is the backend default max upload size limit.
maxUploadSetting := s.Store.GetSystemSettingValueWithDefault(ctx, SystemSettingMaxUploadSizeMiBName.String(), "32")
var settingMaxUploadSizeBytes int
if settingMaxUploadSizeMiB, err := strconv.Atoi(maxUploadSetting); err == nil {
settingMaxUploadSizeBytes = settingMaxUploadSizeMiB * MebiByte
} else {
log.Warn("Failed to parse max upload size", zap.Error(err))
settingMaxUploadSizeBytes = 0
}
file, err := c.FormFile("file")
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get uploading file").SetInternal(err)
}
if file == nil {
return echo.NewHTTPError(http.StatusBadRequest, "Upload file not found").SetInternal(err)
}
if file.Size > int64(settingMaxUploadSizeBytes) {
message := fmt.Sprintf("File size exceeds allowed limit of %d MiB", settingMaxUploadSizeBytes/MebiByte)
return echo.NewHTTPError(http.StatusBadRequest, message).SetInternal(err)
}
if err := c.Request().ParseMultipartForm(maxUploadBufferSizeBytes); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Failed to parse upload data").SetInternal(err)
}
sourceFile, err := file.Open()
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to open file").SetInternal(err)
}
defer sourceFile.Close()
create := &store.Resource{
CreatorID: userID,
Filename: file.Filename,
Type: file.Header.Get("Content-Type"),
Size: file.Size,
}
err = SaveResourceBlob(ctx, s.Store, create, sourceFile)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to save resource").SetInternal(err)
}
resource, err := s.Store.CreateResource(ctx, create)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create resource").SetInternal(err)
}
return c.JSON(http.StatusOK, convertResourceFromStore(resource))
}
// DeleteResource godoc
//
// @Summary Delete a resource
// @Tags resource
// @Produce json
// @Param resourceId path int true "Resource ID"
// @Success 200 {boolean} true "Resource deleted"
// @Failure 400 {object} nil "ID is not a number: %s"
// @Failure 401 {object} nil "Missing user in session"
// @Failure 404 {object} nil "Resource not found: %d"
// @Failure 500 {object} nil "Failed to find resource | Failed to delete resource"
// @Router /api/v1/resource/{resourceId} [DELETE]
func (s *APIV1Service) DeleteResource(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
resourceID, err := util.ConvertStringToInt32(c.Param("resourceId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("resourceId"))).SetInternal(err)
}
resource, err := s.Store.GetResource(ctx, &store.FindResource{
ID: &resourceID,
CreatorID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find resource").SetInternal(err)
}
if resource == nil {
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Resource not found: %d", resourceID))
}
if err := s.Store.DeleteResource(ctx, &store.DeleteResource{
ID: resourceID,
}); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete resource").SetInternal(err)
}
return c.JSON(http.StatusOK, true)
}
// UpdateResource godoc
//
// @Summary Update a resource
// @Tags resource
// @Produce json
// @Param resourceId path int true "Resource ID"
// @Param patch body UpdateResourceRequest true "Patch resource request"
// @Success 200 {object} store.Resource "Updated resource"
// @Failure 400 {object} nil "ID is not a number: %s | Malformatted patch resource request"
// @Failure 401 {object} nil "Missing user in session | Unauthorized"
// @Failure 404 {object} nil "Resource not found: %d"
// @Failure 500 {object} nil "Failed to find resource | Failed to patch resource"
// @Router /api/v1/resource/{resourceId} [PATCH]
func (s *APIV1Service) UpdateResource(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
resourceID, err := util.ConvertStringToInt32(c.Param("resourceId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("resourceId"))).SetInternal(err)
}
resource, err := s.Store.GetResource(ctx, &store.FindResource{
ID: &resourceID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find resource").SetInternal(err)
}
if resource == nil {
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Resource not found: %d", resourceID))
}
if resource.CreatorID != userID {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
request := &UpdateResourceRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch resource request").SetInternal(err)
}
currentTs := time.Now().Unix()
update := &store.UpdateResource{
ID: resourceID,
UpdatedTs: &currentTs,
}
if request.Filename != nil && *request.Filename != "" {
update.Filename = request.Filename
}
resource, err = s.Store.UpdateResource(ctx, update)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch resource").SetInternal(err)
}
return c.JSON(http.StatusOK, convertResourceFromStore(resource))
}
func replacePathTemplate(path, filename string) string {
t := time.Now()
path = fileKeyPattern.ReplaceAllStringFunc(path, func(s string) string {
switch s {
case "{filename}":
return filename
case "{timestamp}":
return fmt.Sprintf("%d", t.Unix())
case "{year}":
return fmt.Sprintf("%d", t.Year())
case "{month}":
return fmt.Sprintf("%02d", t.Month())
case "{day}":
return fmt.Sprintf("%02d", t.Day())
case "{hour}":
return fmt.Sprintf("%02d", t.Hour())
case "{minute}":
return fmt.Sprintf("%02d", t.Minute())
case "{second}":
return fmt.Sprintf("%02d", t.Second())
}
return s
})
return path
}
func convertResourceFromStore(resource *store.Resource) *Resource {
return &Resource{
ID: resource.ID,
CreatorID: resource.CreatorID,
CreatedTs: resource.CreatedTs,
UpdatedTs: resource.UpdatedTs,
Filename: resource.Filename,
Blob: resource.Blob,
InternalPath: resource.InternalPath,
ExternalLink: resource.ExternalLink,
Type: resource.Type,
Size: resource.Size,
}
}
// SaveResourceBlob save the blob of resource based on the storage config
//
// Depend on the storage config, some fields of *store.ResourceCreate will be changed:
// 1. *DatabaseStorage*: `create.Blob`.
// 2. *LocalStorage*: `create.InternalPath`.
// 3. Others( external service): `create.ExternalLink`.
func SaveResourceBlob(ctx context.Context, s *store.Store, create *store.Resource, r io.Reader) error {
systemSettingStorageServiceID, err := s.GetSystemSetting(ctx, &store.FindSystemSetting{Name: SystemSettingStorageServiceIDName.String()})
if err != nil {
return errors.Wrap(err, "Failed to find SystemSettingStorageServiceIDName")
}
storageServiceID := DefaultStorage
if systemSettingStorageServiceID != nil {
err = json.Unmarshal([]byte(systemSettingStorageServiceID.Value), &storageServiceID)
if err != nil {
return errors.Wrap(err, "Failed to unmarshal storage service id")
}
}
// `DatabaseStorage` means store blob into database
if storageServiceID == DatabaseStorage {
fileBytes, err := io.ReadAll(r)
if err != nil {
return errors.Wrap(err, "Failed to read file")
}
create.Blob = fileBytes
return nil
} else if storageServiceID == LocalStorage {
// `LocalStorage` means save blob into local disk
systemSettingLocalStoragePath, err := s.GetSystemSetting(ctx, &store.FindSystemSetting{Name: SystemSettingLocalStoragePathName.String()})
if err != nil {
return errors.Wrap(err, "Failed to find SystemSettingLocalStoragePathName")
}
localStoragePath := "assets/{timestamp}_{filename}"
if systemSettingLocalStoragePath != nil && systemSettingLocalStoragePath.Value != "" {
err = json.Unmarshal([]byte(systemSettingLocalStoragePath.Value), &localStoragePath)
if err != nil {
return errors.Wrap(err, "Failed to unmarshal SystemSettingLocalStoragePathName")
}
}
filePath := filepath.FromSlash(localStoragePath)
if !strings.Contains(filePath, "{filename}") {
filePath = filepath.Join(filePath, "{filename}")
}
filePath = filepath.Join(s.Profile.Data, replacePathTemplate(filePath, create.Filename))
dir := filepath.Dir(filePath)
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
return errors.Wrap(err, "Failed to create directory")
}
dst, err := os.Create(filePath)
if err != nil {
return errors.Wrap(err, "Failed to create file")
}
defer dst.Close()
_, err = io.Copy(dst, r)
if err != nil {
return errors.Wrap(err, "Failed to copy file")
}
create.InternalPath = filePath
return nil
}
// Others: store blob into external service, such as S3
storage, err := s.GetStorage(ctx, &store.FindStorage{ID: &storageServiceID})
if err != nil {
return errors.Wrap(err, "Failed to find StorageServiceID")
}
if storage == nil {
return errors.Errorf("Storage %d not found", storageServiceID)
}
storageMessage, err := ConvertStorageFromStore(storage)
if err != nil {
return errors.Wrap(err, "Failed to ConvertStorageFromStore")
}
if storageMessage.Type != StorageS3 {
return errors.Errorf("Unsupported storage type: %s", storageMessage.Type)
}
s3Config := storageMessage.Config.S3Config
s3Client, err := s3.NewClient(ctx, &s3.Config{
AccessKey: s3Config.AccessKey,
SecretKey: s3Config.SecretKey,
EndPoint: s3Config.EndPoint,
Region: s3Config.Region,
Bucket: s3Config.Bucket,
URLPrefix: s3Config.URLPrefix,
URLSuffix: s3Config.URLSuffix,
})
if err != nil {
return errors.Wrap(err, "Failed to create s3 client")
}
filePath := s3Config.Path
if !strings.Contains(filePath, "{filename}") {
filePath = filepath.Join(filePath, "{filename}")
}
filePath = replacePathTemplate(filePath, create.Filename)
link, err := s3Client.UploadFile(ctx, filePath, create.Type, r)
if err != nil {
return errors.Wrap(err, "Failed to upload via s3 client")
}
create.ExternalLink = link
return nil
}
-210
View File
@@ -1,210 +0,0 @@
package v1
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/gorilla/feeds"
"github.com/labstack/echo/v4"
"github.com/usememos/memos/internal/util"
"github.com/usememos/memos/plugin/gomark/parser"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
"github.com/usememos/memos/plugin/gomark/render/html"
"github.com/usememos/memos/store"
)
const maxRSSItemCount = 100
const maxRSSItemTitleLength = 100
func (s *APIV1Service) registerRSSRoutes(g *echo.Group) {
g.GET("/explore/rss.xml", s.GetExploreRSS)
g.GET("/u/:id/rss.xml", s.GetUserRSS)
}
// GetExploreRSS godoc
//
// @Summary Get RSS
// @Tags rss
// @Produce xml
// @Success 200 {object} nil "RSS"
// @Failure 500 {object} nil "Failed to get system customized profile | Failed to find memo list | Failed to generate rss"
// @Router /explore/rss.xml [GET]
func (s *APIV1Service) GetExploreRSS(c echo.Context) error {
ctx := c.Request().Context()
systemCustomizedProfile, err := s.getSystemCustomizedProfile(ctx)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get system customized profile").SetInternal(err)
}
normalStatus := store.Normal
memoFind := store.FindMemo{
RowStatus: &normalStatus,
VisibilityList: []store.Visibility{store.Public},
}
memoList, err := s.Store.ListMemos(ctx, &memoFind)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo list").SetInternal(err)
}
baseURL := c.Scheme() + "://" + c.Request().Host
rss, err := s.generateRSSFromMemoList(ctx, memoList, baseURL, systemCustomizedProfile)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate rss").SetInternal(err)
}
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationXMLCharsetUTF8)
return c.String(http.StatusOK, rss)
}
// GetUserRSS godoc
//
// @Summary Get RSS for a user
// @Tags rss
// @Produce xml
// @Param id path int true "User ID"
// @Success 200 {object} nil "RSS"
// @Failure 400 {object} nil "User id is not a number"
// @Failure 500 {object} nil "Failed to get system customized profile | Failed to find memo list | Failed to generate rss"
// @Router /u/{id}/rss.xml [GET]
func (s *APIV1Service) GetUserRSS(c echo.Context) error {
ctx := c.Request().Context()
id, err := util.ConvertStringToInt32(c.Param("id"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "User id is not a number").SetInternal(err)
}
systemCustomizedProfile, err := s.getSystemCustomizedProfile(ctx)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get system customized profile").SetInternal(err)
}
normalStatus := store.Normal
memoFind := store.FindMemo{
CreatorID: &id,
RowStatus: &normalStatus,
VisibilityList: []store.Visibility{store.Public},
}
memoList, err := s.Store.ListMemos(ctx, &memoFind)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo list").SetInternal(err)
}
baseURL := c.Scheme() + "://" + c.Request().Host
rss, err := s.generateRSSFromMemoList(ctx, memoList, baseURL, systemCustomizedProfile)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate rss").SetInternal(err)
}
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationXMLCharsetUTF8)
return c.String(http.StatusOK, rss)
}
func (s *APIV1Service) generateRSSFromMemoList(ctx context.Context, memoList []*store.Memo, baseURL string, profile *CustomizedProfile) (string, error) {
feed := &feeds.Feed{
Title: profile.Name,
Link: &feeds.Link{Href: baseURL},
Description: profile.Description,
Created: time.Now(),
}
var itemCountLimit = util.Min(len(memoList), maxRSSItemCount)
feed.Items = make([]*feeds.Item, itemCountLimit)
for i := 0; i < itemCountLimit; i++ {
memoMessage, err := s.convertMemoFromStore(ctx, memoList[i])
if err != nil {
return "", err
}
description, err := getRSSItemDescription(memoMessage.Content)
if err != nil {
return "", err
}
feed.Items[i] = &feeds.Item{
Title: getRSSItemTitle(memoMessage.Content),
Link: &feeds.Link{Href: baseURL + "/m/" + fmt.Sprintf("%d", memoMessage.ID)},
Description: description,
Created: time.Unix(memoMessage.CreatedTs, 0),
Enclosure: &feeds.Enclosure{Url: baseURL + "/m/" + fmt.Sprintf("%d", memoMessage.ID) + "/image"},
}
if len(memoMessage.ResourceList) > 0 {
resource := memoMessage.ResourceList[0]
enclosure := feeds.Enclosure{}
if resource.ExternalLink != "" {
enclosure.Url = resource.ExternalLink
} else {
enclosure.Url = baseURL + "/o/r/" + fmt.Sprintf("%d", resource.ID)
}
enclosure.Length = strconv.Itoa(int(resource.Size))
enclosure.Type = resource.Type
feed.Items[i].Enclosure = &enclosure
}
}
rss, err := feed.ToRss()
if err != nil {
return "", err
}
return rss, nil
}
func (s *APIV1Service) getSystemCustomizedProfile(ctx context.Context) (*CustomizedProfile, error) {
systemSetting, err := s.Store.GetSystemSetting(ctx, &store.FindSystemSetting{
Name: SystemSettingCustomizedProfileName.String(),
})
if err != nil {
return nil, err
}
customizedProfile := &CustomizedProfile{
Name: "memos",
LogoURL: "",
Description: "",
Locale: "en",
Appearance: "system",
ExternalURL: "",
}
if systemSetting != nil {
if err := json.Unmarshal([]byte(systemSetting.Value), customizedProfile); err != nil {
return nil, err
}
}
return customizedProfile, nil
}
func getRSSItemTitle(content string) string {
var title string
if isTitleDefined(content) {
title = strings.Split(content, "\n")[0][2:]
} else {
title = strings.Split(content, "\n")[0]
var titleLengthLimit = util.Min(len(title), maxRSSItemTitleLength)
if titleLengthLimit < len(title) {
title = title[:titleLengthLimit] + "..."
}
}
return title
}
func getRSSItemDescription(content string) (string, error) {
var description string
if isTitleDefined(content) {
var firstLineEnd = strings.Index(content, "\n")
description = strings.Trim(content[firstLineEnd+1:], " ")
} else {
description = content
}
tokens := tokenizer.Tokenize(description)
nodes, err := parser.Parse(tokens)
if err != nil {
return "", err
}
result := html.NewHTMLRender().Render(nodes)
return result, nil
}
func isTitleDefined(content string) bool {
return strings.HasPrefix(content, "# ")
}
-315
View File
@@ -1,315 +0,0 @@
package v1
import (
"encoding/json"
"fmt"
"net/http"
"github.com/labstack/echo/v4"
"github.com/usememos/memos/internal/util"
"github.com/usememos/memos/store"
)
const (
// LocalStorage means the storage service is local file system.
LocalStorage int32 = -1
// DatabaseStorage means the storage service is database.
DatabaseStorage int32 = 0
// Default storage service is database.
DefaultStorage int32 = DatabaseStorage
)
type StorageType string
const (
StorageS3 StorageType = "S3"
)
func (t StorageType) String() string {
return string(t)
}
type StorageConfig struct {
S3Config *StorageS3Config `json:"s3Config"`
}
type StorageS3Config struct {
EndPoint string `json:"endPoint"`
Path string `json:"path"`
Region string `json:"region"`
AccessKey string `json:"accessKey"`
SecretKey string `json:"secretKey"`
Bucket string `json:"bucket"`
URLPrefix string `json:"urlPrefix"`
URLSuffix string `json:"urlSuffix"`
}
type Storage struct {
ID int32 `json:"id"`
Name string `json:"name"`
Type StorageType `json:"type"`
Config *StorageConfig `json:"config"`
}
type CreateStorageRequest struct {
Name string `json:"name"`
Type StorageType `json:"type"`
Config *StorageConfig `json:"config"`
}
type UpdateStorageRequest struct {
Type StorageType `json:"type"`
Name *string `json:"name"`
Config *StorageConfig `json:"config"`
}
func (s *APIV1Service) registerStorageRoutes(g *echo.Group) {
g.GET("/storage", s.GetStorageList)
g.POST("/storage", s.CreateStorage)
g.PATCH("/storage/:storageId", s.UpdateStorage)
g.DELETE("/storage/:storageId", s.DeleteStorage)
}
// GetStorageList godoc
//
// @Summary Get a list of storages
// @Tags storage
// @Produce json
// @Success 200 {object} []store.Storage "List of storages"
// @Failure 401 {object} nil "Missing user in session | Unauthorized"
// @Failure 500 {object} nil "Failed to find user | Failed to convert storage"
// @Router /api/v1/storage [GET]
func (s *APIV1Service) GetStorageList(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
user, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
// We should only show storage list to host user.
if user == nil || user.Role != store.RoleHost {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
list, err := s.Store.ListStorages(ctx, &store.FindStorage{})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find storage list").SetInternal(err)
}
storageList := []*Storage{}
for _, storage := range list {
storageMessage, err := ConvertStorageFromStore(storage)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to convert storage").SetInternal(err)
}
storageList = append(storageList, storageMessage)
}
return c.JSON(http.StatusOK, storageList)
}
// CreateStorage godoc
//
// @Summary Create storage
// @Tags storage
// @Accept json
// @Produce json
// @Param body body CreateStorageRequest true "Request object."
// @Success 200 {object} store.Storage "Created storage"
// @Failure 400 {object} nil "Malformatted post storage request"
// @Failure 401 {object} nil "Missing user in session"
// @Failure 500 {object} nil "Failed to find user | Failed to create storage | Failed to convert storage"
// @Router /api/v1/storage [POST]
func (s *APIV1Service) CreateStorage(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
user, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if user == nil || user.Role != store.RoleHost {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
create := &CreateStorageRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(create); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post storage request").SetInternal(err)
}
configString := ""
if create.Type == StorageS3 && create.Config.S3Config != nil {
configBytes, err := json.Marshal(create.Config.S3Config)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post storage request").SetInternal(err)
}
configString = string(configBytes)
}
storage, err := s.Store.CreateStorage(ctx, &store.Storage{
Name: create.Name,
Type: create.Type.String(),
Config: configString,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create storage").SetInternal(err)
}
storageMessage, err := ConvertStorageFromStore(storage)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to convert storage").SetInternal(err)
}
return c.JSON(http.StatusOK, storageMessage)
}
// DeleteStorage godoc
//
// @Summary Delete a storage
// @Tags storage
// @Produce json
// @Param storageId path int true "Storage ID"
// @Success 200 {boolean} true "Storage deleted"
// @Failure 400 {object} nil "ID is not a number: %s | Storage service %d is using"
// @Failure 401 {object} nil "Missing user in session | Unauthorized"
// @Failure 500 {object} nil "Failed to find user | Failed to find storage | Failed to unmarshal storage service id | Failed to delete storage"
// @Router /api/v1/storage/{storageId} [DELETE]
//
// NOTES:
// - error message "Storage service %d is using" probably should be "Storage service %d is in use".
func (s *APIV1Service) DeleteStorage(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
user, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if user == nil || user.Role != store.RoleHost {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
storageID, err := util.ConvertStringToInt32(c.Param("storageId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("storageId"))).SetInternal(err)
}
systemSetting, err := s.Store.GetSystemSetting(ctx, &store.FindSystemSetting{Name: SystemSettingStorageServiceIDName.String()})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find storage").SetInternal(err)
}
if systemSetting != nil {
storageServiceID := DefaultStorage
err = json.Unmarshal([]byte(systemSetting.Value), &storageServiceID)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal storage service id").SetInternal(err)
}
if storageServiceID == storageID {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Storage service %d is using", storageID))
}
}
if err = s.Store.DeleteStorage(ctx, &store.DeleteStorage{ID: storageID}); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete storage").SetInternal(err)
}
return c.JSON(http.StatusOK, true)
}
// UpdateStorage godoc
//
// @Summary Update a storage
// @Tags storage
// @Produce json
// @Param storageId path int true "Storage ID"
// @Param patch body UpdateStorageRequest true "Patch request"
// @Success 200 {object} store.Storage "Updated resource"
// @Failure 400 {object} nil "ID is not a number: %s | Malformatted patch storage request | Malformatted post storage request"
// @Failure 401 {object} nil "Missing user in session | Unauthorized"
// @Failure 500 {object} nil "Failed to find user | Failed to patch storage | Failed to convert storage"
// @Router /api/v1/storage/{storageId} [PATCH]
func (s *APIV1Service) UpdateStorage(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
user, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if user == nil || user.Role != store.RoleHost {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
storageID, err := util.ConvertStringToInt32(c.Param("storageId"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("storageId"))).SetInternal(err)
}
update := &UpdateStorageRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(update); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch storage request").SetInternal(err)
}
storageUpdate := &store.UpdateStorage{
ID: storageID,
}
if update.Name != nil {
storageUpdate.Name = update.Name
}
if update.Config != nil {
if update.Type == StorageS3 {
configBytes, err := json.Marshal(update.Config.S3Config)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post storage request").SetInternal(err)
}
configString := string(configBytes)
storageUpdate.Config = &configString
}
}
storage, err := s.Store.UpdateStorage(ctx, storageUpdate)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch storage").SetInternal(err)
}
storageMessage, err := ConvertStorageFromStore(storage)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to convert storage").SetInternal(err)
}
return c.JSON(http.StatusOK, storageMessage)
}
func ConvertStorageFromStore(storage *store.Storage) (*Storage, error) {
storageMessage := &Storage{
ID: storage.ID,
Name: storage.Name,
Type: StorageType(storage.Type),
Config: &StorageConfig{},
}
if storageMessage.Type == StorageS3 {
s3Config := &StorageS3Config{}
if err := json.Unmarshal([]byte(storage.Config), s3Config); err != nil {
return nil, err
}
storageMessage.Config = &StorageConfig{
S3Config: s3Config,
}
}
return storageMessage, nil
}
-2033
View File
File diff suppressed because it is too large Load Diff
-183
View File
@@ -1,183 +0,0 @@
package v1
import (
"encoding/json"
"net/http"
"github.com/labstack/echo/v4"
"go.uber.org/zap"
"github.com/usememos/memos/internal/log"
"github.com/usememos/memos/server/profile"
"github.com/usememos/memos/store"
)
type SystemStatus struct {
Host *User `json:"host"`
Profile profile.Profile `json:"profile"`
DBSize int64 `json:"dbSize"`
// System settings
// Allow sign up.
AllowSignUp bool `json:"allowSignUp"`
// Disable password login.
DisablePasswordLogin bool `json:"disablePasswordLogin"`
// Disable public memos.
DisablePublicMemos bool `json:"disablePublicMemos"`
// Max upload size.
MaxUploadSizeMiB int `json:"maxUploadSizeMiB"`
// Auto Backup Interval.
AutoBackupInterval int `json:"autoBackupInterval"`
// Additional style.
AdditionalStyle string `json:"additionalStyle"`
// Additional script.
AdditionalScript string `json:"additionalScript"`
// Customized server profile, including server name and external url.
CustomizedProfile CustomizedProfile `json:"customizedProfile"`
// Storage service ID.
StorageServiceID int32 `json:"storageServiceId"`
// Local storage path.
LocalStoragePath string `json:"localStoragePath"`
// Memo display with updated timestamp.
MemoDisplayWithUpdatedTs bool `json:"memoDisplayWithUpdatedTs"`
}
func (s *APIV1Service) registerSystemRoutes(g *echo.Group) {
g.GET("/ping", s.PingSystem)
g.GET("/status", s.GetSystemStatus)
g.POST("/system/vacuum", s.ExecVacuum)
}
// PingSystem godoc
//
// @Summary Ping the system
// @Tags system
// @Produce json
// @Success 200 {boolean} true "If succeed to ping the system"
// @Router /api/v1/ping [GET]
func (*APIV1Service) PingSystem(c echo.Context) error {
return c.JSON(http.StatusOK, true)
}
// GetSystemStatus godoc
//
// @Summary Get system GetSystemStatus
// @Tags system
// @Produce json
// @Success 200 {object} SystemStatus "System GetSystemStatus"
// @Failure 401 {object} nil "Missing user in session | Unauthorized"
// @Failure 500 {object} nil "Failed to find host user | Failed to find system setting list | Failed to unmarshal system setting customized profile value"
// @Router /api/v1/status [GET]
func (s *APIV1Service) GetSystemStatus(c echo.Context) error {
ctx := c.Request().Context()
systemStatus := SystemStatus{
Profile: profile.Profile{
Mode: s.Profile.Mode,
Version: s.Profile.Version,
},
// Allow sign up by default.
AllowSignUp: true,
MaxUploadSizeMiB: 32,
CustomizedProfile: CustomizedProfile{
Name: "memos",
Locale: "en",
Appearance: "system",
},
StorageServiceID: DefaultStorage,
LocalStoragePath: "assets/{timestamp}_{filename}",
}
hostUserType := store.RoleHost
hostUser, err := s.Store.GetUser(ctx, &store.FindUser{
Role: &hostUserType,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find host user").SetInternal(err)
}
if hostUser != nil {
systemStatus.Host = &User{ID: hostUser.ID}
}
systemSettingList, err := s.Store.ListSystemSettings(ctx, &store.FindSystemSetting{})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find system setting list").SetInternal(err)
}
for _, systemSetting := range systemSettingList {
if systemSetting.Name == SystemSettingServerIDName.String() || systemSetting.Name == SystemSettingSecretSessionName.String() || systemSetting.Name == SystemSettingTelegramBotTokenName.String() || systemSetting.Name == SystemSettingInstanceURLName.String() {
continue
}
var baseValue any
err := json.Unmarshal([]byte(systemSetting.Value), &baseValue)
if err != nil {
log.Warn("Failed to unmarshal system setting value", zap.String("setting name", systemSetting.Name))
continue
}
switch systemSetting.Name {
case SystemSettingAllowSignUpName.String():
systemStatus.AllowSignUp = baseValue.(bool)
case SystemSettingDisablePasswordLoginName.String():
systemStatus.DisablePasswordLogin = baseValue.(bool)
case SystemSettingDisablePublicMemosName.String():
systemStatus.DisablePublicMemos = baseValue.(bool)
case SystemSettingMaxUploadSizeMiBName.String():
systemStatus.MaxUploadSizeMiB = int(baseValue.(float64))
case SystemSettingAutoBackupIntervalName.String():
systemStatus.AutoBackupInterval = int(baseValue.(float64))
case SystemSettingAdditionalStyleName.String():
systemStatus.AdditionalStyle = baseValue.(string)
case SystemSettingAdditionalScriptName.String():
systemStatus.AdditionalScript = baseValue.(string)
case SystemSettingCustomizedProfileName.String():
customizedProfile := CustomizedProfile{}
if err := json.Unmarshal([]byte(systemSetting.Value), &customizedProfile); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal system setting customized profile value").SetInternal(err)
}
systemStatus.CustomizedProfile = customizedProfile
case SystemSettingStorageServiceIDName.String():
systemStatus.StorageServiceID = int32(baseValue.(float64))
case SystemSettingLocalStoragePathName.String():
systemStatus.LocalStoragePath = baseValue.(string)
case SystemSettingMemoDisplayWithUpdatedTsName.String():
systemStatus.MemoDisplayWithUpdatedTs = baseValue.(bool)
default:
log.Warn("Unknown system setting name", zap.String("setting name", systemSetting.Name))
}
}
return c.JSON(http.StatusOK, systemStatus)
}
// ExecVacuum godoc
//
// @Summary Vacuum the database
// @Tags system
// @Produce json
// @Success 200 {boolean} true "Database vacuumed"
// @Failure 401 {object} nil "Missing user in session | Unauthorized"
// @Failure 500 {object} nil "Failed to find user | Failed to ExecVacuum database"
// @Router /api/v1/system/vacuum [POST]
func (s *APIV1Service) ExecVacuum(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
user, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if user == nil || user.Role != store.RoleHost {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
if err := s.Store.Vacuum(ctx); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to vacuum database").SetInternal(err)
}
return c.JSON(http.StatusOK, true)
}
-289
View File
@@ -1,289 +0,0 @@
package v1
import (
"encoding/json"
"net/http"
"strings"
"github.com/labstack/echo/v4"
"github.com/pkg/errors"
"github.com/usememos/memos/store"
)
type SystemSettingName string
const (
// SystemSettingServerIDName is the name of server id.
SystemSettingServerIDName SystemSettingName = "server-id"
// SystemSettingSecretSessionName is the name of secret session.
SystemSettingSecretSessionName SystemSettingName = "secret-session"
// SystemSettingAllowSignUpName is the name of allow signup setting.
SystemSettingAllowSignUpName SystemSettingName = "allow-signup"
// SystemSettingDisablePasswordLoginName is the name of disable password login setting.
SystemSettingDisablePasswordLoginName SystemSettingName = "disable-password-login"
// SystemSettingDisablePublicMemosName is the name of disable public memos setting.
SystemSettingDisablePublicMemosName SystemSettingName = "disable-public-memos"
// SystemSettingMaxUploadSizeMiBName is the name of max upload size setting.
SystemSettingMaxUploadSizeMiBName SystemSettingName = "max-upload-size-mib"
// SystemSettingAdditionalStyleName is the name of additional style.
SystemSettingAdditionalStyleName SystemSettingName = "additional-style"
// SystemSettingAdditionalScriptName is the name of additional script.
SystemSettingAdditionalScriptName SystemSettingName = "additional-script"
// SystemSettingCustomizedProfileName is the name of customized server profile.
SystemSettingCustomizedProfileName SystemSettingName = "customized-profile"
// SystemSettingStorageServiceIDName is the name of storage service ID.
SystemSettingStorageServiceIDName SystemSettingName = "storage-service-id"
// SystemSettingLocalStoragePathName is the name of local storage path.
SystemSettingLocalStoragePathName SystemSettingName = "local-storage-path"
// SystemSettingTelegramBotTokenName is the name of Telegram Bot Token.
SystemSettingTelegramBotTokenName SystemSettingName = "telegram-bot-token"
// SystemSettingMemoDisplayWithUpdatedTsName is the name of memo display with updated ts.
SystemSettingMemoDisplayWithUpdatedTsName SystemSettingName = "memo-display-with-updated-ts"
// SystemSettingAutoBackupIntervalName is the name of auto backup interval as seconds.
SystemSettingAutoBackupIntervalName SystemSettingName = "auto-backup-interval"
// SystemSettingInstanceURLName is the name of instance url setting.
SystemSettingInstanceURLName SystemSettingName = "instance-url"
)
const systemSettingUnmarshalError = `failed to unmarshal value from system setting "%v"`
// CustomizedProfile is the struct definition for SystemSettingCustomizedProfileName system setting item.
type CustomizedProfile struct {
// Name is the server name, default is `memos`
Name string `json:"name"`
// LogoURL is the url of logo image.
LogoURL string `json:"logoUrl"`
// Description is the server description.
Description string `json:"description"`
// Locale is the server default locale.
Locale string `json:"locale"`
// Appearance is the server default appearance.
Appearance string `json:"appearance"`
// ExternalURL is the external url of server. e.g. https://usermemos.com
ExternalURL string `json:"externalUrl"`
}
func (key SystemSettingName) String() string {
return string(key)
}
type SystemSetting struct {
Name SystemSettingName `json:"name"`
// Value is a JSON string with basic value.
Value string `json:"value"`
Description string `json:"description"`
}
type UpsertSystemSettingRequest struct {
Name SystemSettingName `json:"name"`
Value string `json:"value"`
Description string `json:"description"`
}
func (s *APIV1Service) registerSystemSettingRoutes(g *echo.Group) {
g.GET("/system/setting", s.GetSystemSettingList)
g.POST("/system/setting", s.CreateSystemSetting)
}
// GetSystemSettingList godoc
//
// @Summary Get a list of system settings
// @Tags system-setting
// @Produce json
// @Success 200 {object} []SystemSetting "System setting list"
// @Failure 401 {object} nil "Missing user in session | Unauthorized"
// @Failure 500 {object} nil "Failed to find user | Failed to find system setting list"
// @Router /api/v1/system/setting [GET]
func (s *APIV1Service) GetSystemSettingList(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
user, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if user == nil || user.Role != store.RoleHost {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
list, err := s.Store.ListSystemSettings(ctx, &store.FindSystemSetting{})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find system setting list").SetInternal(err)
}
systemSettingList := make([]*SystemSetting, 0, len(list))
for _, systemSetting := range list {
systemSettingList = append(systemSettingList, convertSystemSettingFromStore(systemSetting))
}
return c.JSON(http.StatusOK, systemSettingList)
}
// CreateSystemSetting godoc
//
// @Summary Create system setting
// @Tags system-setting
// @Accept json
// @Produce json
// @Param body body UpsertSystemSettingRequest true "Request object."
// @Success 200 {object} store.SystemSetting "Created system setting"
// @Failure 400 {object} nil "Malformatted post system setting request | invalid system setting"
// @Failure 401 {object} nil "Missing user in session | Unauthorized"
// @Failure 403 {object} nil "Cannot disable passwords if no SSO identity provider is configured."
// @Failure 500 {object} nil "Failed to find user | Failed to upsert system setting"
// @Router /api/v1/system/setting [POST]
func (s *APIV1Service) CreateSystemSetting(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
user, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if user == nil || user.Role != store.RoleHost {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}
systemSettingUpsert := &UpsertSystemSettingRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(systemSettingUpsert); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post system setting request").SetInternal(err)
}
if err := systemSettingUpsert.Validate(); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "invalid system setting").SetInternal(err)
}
if systemSettingUpsert.Name == SystemSettingDisablePasswordLoginName {
var disablePasswordLogin bool
if err := json.Unmarshal([]byte(systemSettingUpsert.Value), &disablePasswordLogin); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "invalid system setting").SetInternal(err)
}
identityProviderList, err := s.Store.ListIdentityProviders(ctx, &store.FindIdentityProvider{})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert system setting").SetInternal(err)
}
if disablePasswordLogin && len(identityProviderList) == 0 {
return echo.NewHTTPError(http.StatusForbidden, "Cannot disable passwords if no SSO identity provider is configured.")
}
}
systemSetting, err := s.Store.UpsertSystemSetting(ctx, &store.SystemSetting{
Name: systemSettingUpsert.Name.String(),
Value: systemSettingUpsert.Value,
Description: systemSettingUpsert.Description,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert system setting").SetInternal(err)
}
return c.JSON(http.StatusOK, convertSystemSettingFromStore(systemSetting))
}
func (upsert UpsertSystemSettingRequest) Validate() error {
switch settingName := upsert.Name; settingName {
case SystemSettingServerIDName:
return errors.Errorf("updating %v is not allowed", settingName)
case SystemSettingAllowSignUpName:
var value bool
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return errors.Errorf(systemSettingUnmarshalError, settingName)
}
case SystemSettingDisablePasswordLoginName:
var value bool
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return errors.Errorf(systemSettingUnmarshalError, settingName)
}
case SystemSettingDisablePublicMemosName:
var value bool
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return errors.Errorf(systemSettingUnmarshalError, settingName)
}
case SystemSettingMaxUploadSizeMiBName:
var value int
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return errors.Errorf(systemSettingUnmarshalError, settingName)
}
case SystemSettingAdditionalStyleName:
var value string
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return errors.Errorf(systemSettingUnmarshalError, settingName)
}
case SystemSettingAdditionalScriptName:
var value string
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return errors.Errorf(systemSettingUnmarshalError, settingName)
}
case SystemSettingCustomizedProfileName:
customizedProfile := CustomizedProfile{
Name: "memos",
LogoURL: "",
Description: "",
Locale: "en",
Appearance: "system",
ExternalURL: "",
}
if err := json.Unmarshal([]byte(upsert.Value), &customizedProfile); err != nil {
return errors.Errorf(systemSettingUnmarshalError, settingName)
}
case SystemSettingStorageServiceIDName:
// Note: 0 is the default value(database) for storage service ID.
value := 0
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return errors.Errorf(systemSettingUnmarshalError, settingName)
}
return nil
case SystemSettingLocalStoragePathName:
value := ""
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return errors.Errorf(systemSettingUnmarshalError, settingName)
}
case SystemSettingAutoBackupIntervalName:
var value int
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return errors.Errorf(systemSettingUnmarshalError, settingName)
}
if value < 0 {
return errors.New("must be positive")
}
case SystemSettingTelegramBotTokenName:
if upsert.Value == "" {
return nil
}
// Bot Token with Reverse Proxy shoule like `http.../bot<token>`
if strings.HasPrefix(upsert.Value, "http") {
slashIndex := strings.LastIndexAny(upsert.Value, "/")
if strings.HasPrefix(upsert.Value[slashIndex:], "/bot") {
return nil
}
return errors.New("token start with `http` must end with `/bot<token>`")
}
fragments := strings.Split(upsert.Value, ":")
if len(fragments) != 2 {
return errors.Errorf(systemSettingUnmarshalError, settingName)
}
case SystemSettingMemoDisplayWithUpdatedTsName:
var value bool
if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil {
return errors.Errorf(systemSettingUnmarshalError, settingName)
}
case SystemSettingInstanceURLName:
default:
return errors.New("invalid system setting name")
}
return nil
}
func convertSystemSettingFromStore(systemSetting *store.SystemSetting) *SystemSetting {
return &SystemSetting{
Name: SystemSettingName(systemSetting.Name),
Value: systemSetting.Value,
Description: systemSetting.Description,
}
}
-218
View File
@@ -1,218 +0,0 @@
package v1
import (
"encoding/json"
"fmt"
"net/http"
"regexp"
"sort"
"github.com/labstack/echo/v4"
"golang.org/x/exp/slices"
"github.com/usememos/memos/store"
)
type Tag struct {
Name string
CreatorID int32
}
type UpsertTagRequest struct {
Name string `json:"name"`
}
type DeleteTagRequest struct {
Name string `json:"name"`
}
func (s *APIV1Service) registerTagRoutes(g *echo.Group) {
g.GET("/tag", s.GetTagList)
g.POST("/tag", s.CreateTag)
g.GET("/tag/suggestion", s.GetTagSuggestion)
g.POST("/tag/delete", s.DeleteTag)
}
// GetTagList godoc
//
// @Summary Get a list of tags
// @Tags tag
// @Produce json
// @Success 200 {object} []string "Tag list"
// @Failure 400 {object} nil "Missing user id to find tag"
// @Failure 500 {object} nil "Failed to find tag list"
// @Router /api/v1/tag [GET]
func (s *APIV1Service) GetTagList(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusBadRequest, "Missing user id to find tag")
}
list, err := s.Store.ListTags(ctx, &store.FindTag{
CreatorID: userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find tag list").SetInternal(err)
}
tagNameList := []string{}
for _, tag := range list {
tagNameList = append(tagNameList, tag.Name)
}
return c.JSON(http.StatusOK, tagNameList)
}
// CreateTag godoc
//
// @Summary Create a tag
// @Tags tag
// @Accept json
// @Produce json
// @Param body body UpsertTagRequest true "Request object."
// @Success 200 {object} string "Created tag name"
// @Failure 400 {object} nil "Malformatted post tag request | Tag name shouldn't be empty"
// @Failure 401 {object} nil "Missing user in session"
// @Failure 500 {object} nil "Failed to upsert tag | Failed to create activity"
// @Router /api/v1/tag [POST]
func (s *APIV1Service) CreateTag(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
tagUpsert := &UpsertTagRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(tagUpsert); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post tag request").SetInternal(err)
}
if tagUpsert.Name == "" {
return echo.NewHTTPError(http.StatusBadRequest, "Tag name shouldn't be empty")
}
tag, err := s.Store.UpsertTag(ctx, &store.Tag{
Name: tagUpsert.Name,
CreatorID: userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to upsert tag").SetInternal(err)
}
tagMessage := convertTagFromStore(tag)
return c.JSON(http.StatusOK, tagMessage.Name)
}
// DeleteTag godoc
//
// @Summary Delete a tag
// @Tags tag
// @Accept json
// @Produce json
// @Param body body DeleteTagRequest true "Request object."
// @Success 200 {boolean} true "Tag deleted"
// @Failure 400 {object} nil "Malformatted post tag request | Tag name shouldn't be empty"
// @Failure 401 {object} nil "Missing user in session"
// @Failure 500 {object} nil "Failed to delete tag name: %v"
// @Router /api/v1/tag/delete [POST]
func (s *APIV1Service) DeleteTag(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
tagDelete := &DeleteTagRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(tagDelete); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post tag request").SetInternal(err)
}
if tagDelete.Name == "" {
return echo.NewHTTPError(http.StatusBadRequest, "Tag name shouldn't be empty")
}
err := s.Store.DeleteTag(ctx, &store.DeleteTag{
Name: tagDelete.Name,
CreatorID: userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to delete tag name: %v", tagDelete.Name)).SetInternal(err)
}
return c.JSON(http.StatusOK, true)
}
// GetTagSuggestion godoc
//
// @Summary Get a list of tags suggested from other memos contents
// @Tags tag
// @Produce json
// @Success 200 {object} []string "Tag list"
// @Failure 400 {object} nil "Missing user session"
// @Failure 500 {object} nil "Failed to find memo list | Failed to find tag list"
// @Router /api/v1/tag/suggestion [GET]
func (s *APIV1Service) GetTagSuggestion(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusBadRequest, "Missing user session")
}
normalRowStatus := store.Normal
memoFind := &store.FindMemo{
CreatorID: &userID,
ContentSearch: []string{"#"},
RowStatus: &normalRowStatus,
}
memoMessageList, err := s.Store.ListMemos(ctx, memoFind)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo list").SetInternal(err)
}
list, err := s.Store.ListTags(ctx, &store.FindTag{
CreatorID: userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find tag list").SetInternal(err)
}
tagNameList := []string{}
for _, tag := range list {
tagNameList = append(tagNameList, tag.Name)
}
tagMapSet := make(map[string]bool)
for _, memo := range memoMessageList {
for _, tag := range findTagListFromMemoContent(memo.Content) {
if !slices.Contains(tagNameList, tag) {
tagMapSet[tag] = true
}
}
}
tagList := []string{}
for tag := range tagMapSet {
tagList = append(tagList, tag)
}
sort.Strings(tagList)
return c.JSON(http.StatusOK, tagList)
}
func convertTagFromStore(tag *store.Tag) *Tag {
return &Tag{
Name: tag.Name,
CreatorID: tag.CreatorID,
}
}
var tagRegexp = regexp.MustCompile(`#([^\s#,]+)`)
func findTagListFromMemoContent(memoContent string) []string {
tagMapSet := make(map[string]bool)
matches := tagRegexp.FindAllStringSubmatch(memoContent, -1)
for _, v := range matches {
tagName := v[1]
tagMapSet[tagName] = true
}
tagList := []string{}
for tag := range tagMapSet {
tagList = append(tagList, tag)
}
sort.Strings(tagList)
return tagList
}
-47
View File
@@ -1,47 +0,0 @@
package v1
import (
"testing"
)
func TestFindTagListFromMemoContent(t *testing.T) {
tests := []struct {
memoContent string
want []string
}{
{
memoContent: "#tag1 ",
want: []string{"tag1"},
},
{
memoContent: "#tag1 #tag2 ",
want: []string{"tag1", "tag2"},
},
{
memoContent: "#tag1 #tag2 \n#tag3 ",
want: []string{"tag1", "tag2", "tag3"},
},
{
memoContent: "#tag1 #tag2 \n#tag3 #tag4 ",
want: []string{"tag1", "tag2", "tag3", "tag4"},
},
{
memoContent: "#tag1 #tag2 \n#tag3 #tag4 ",
want: []string{"tag1", "tag2", "tag3", "tag4"},
},
{
memoContent: "#tag1 123123#tag2 \n#tag3 #tag4 ",
want: []string{"tag1", "tag2", "tag3", "tag4"},
},
{
memoContent: "#tag1 http://123123.com?123123#tag2 \n#tag3 #tag4 http://123123.com?123123#tag2) ",
want: []string{"tag1", "tag2", "tag2)", "tag3", "tag4"},
},
}
for _, test := range tests {
result := findTagListFromMemoContent(test.memoContent)
if len(result) != len(test.want) {
t.Errorf("Find tag list %s: got result %v, want %v.", test.memoContent, result, test.want)
}
}
}
-484
View File
@@ -1,484 +0,0 @@
package v1
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/labstack/echo/v4"
"github.com/pkg/errors"
"golang.org/x/crypto/bcrypt"
"github.com/usememos/memos/internal/util"
"github.com/usememos/memos/server/service/metric"
"github.com/usememos/memos/store"
)
// Role is the type of a role.
type Role string
const (
// RoleHost is the HOST role.
RoleHost Role = "HOST"
// RoleAdmin is the ADMIN role.
RoleAdmin Role = "ADMIN"
// RoleUser is the USER role.
RoleUser Role = "USER"
)
func (role Role) String() string {
return string(role)
}
type User struct {
ID int32 `json:"id"`
// Standard fields
RowStatus RowStatus `json:"rowStatus"`
CreatedTs int64 `json:"createdTs"`
UpdatedTs int64 `json:"updatedTs"`
// Domain specific fields
Username string `json:"username"`
Role Role `json:"role"`
Email string `json:"email"`
Nickname string `json:"nickname"`
PasswordHash string `json:"-"`
AvatarURL string `json:"avatarUrl"`
}
type CreateUserRequest struct {
Username string `json:"username"`
Role Role `json:"role"`
Email string `json:"email"`
Nickname string `json:"nickname"`
Password string `json:"password"`
}
type UpdateUserRequest struct {
RowStatus *RowStatus `json:"rowStatus"`
Username *string `json:"username"`
Email *string `json:"email"`
Nickname *string `json:"nickname"`
Password *string `json:"password"`
AvatarURL *string `json:"avatarUrl"`
}
func (s *APIV1Service) registerUserRoutes(g *echo.Group) {
g.GET("/user", s.GetUserList)
g.POST("/user", s.CreateUser)
g.GET("/user/me", s.GetCurrentUser)
// NOTE: This should be moved to /api/v2/user/:username
g.GET("/user/name/:username", s.GetUserByUsername)
g.GET("/user/:id", s.GetUserByID)
g.PATCH("/user/:id", s.UpdateUser)
g.DELETE("/user/:id", s.DeleteUser)
}
// GetUserList godoc
//
// @Summary Get a list of users
// @Tags user
// @Produce json
// @Success 200 {object} []store.User "User list"
// @Failure 500 {object} nil "Failed to fetch user list"
// @Router /api/v1/user [GET]
func (s *APIV1Service) GetUserList(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
}
currentUser, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user by id").SetInternal(err)
}
if currentUser == nil {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
}
if currentUser.Role != store.RoleHost && currentUser.Role != store.RoleAdmin {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized to list users")
}
list, err := s.Store.ListUsers(ctx, &store.FindUser{})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch user list").SetInternal(err)
}
userMessageList := make([]*User, 0, len(list))
for _, user := range list {
userMessage := convertUserFromStore(user)
// data desensitize
userMessage.Email = ""
userMessageList = append(userMessageList, userMessage)
}
return c.JSON(http.StatusOK, userMessageList)
}
// CreateUser godoc
//
// @Summary Create a user
// @Tags user
// @Accept json
// @Produce json
// @Param body body CreateUserRequest true "Request object"
// @Success 200 {object} store.User "Created user"
// @Failure 400 {object} nil "Malformatted post user request | Invalid user create format"
// @Failure 401 {object} nil "Missing auth session | Unauthorized to create user"
// @Failure 403 {object} nil "Could not create host user"
// @Failure 500 {object} nil "Failed to find user by id | Failed to generate password hash | Failed to create user | Failed to create activity"
// @Router /api/v1/user [POST]
func (s *APIV1Service) CreateUser(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
}
currentUser, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &userID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user by id").SetInternal(err)
}
if currentUser == nil {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
}
if currentUser.Role != store.RoleHost {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized to create user")
}
userCreate := &CreateUserRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(userCreate); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post user request").SetInternal(err)
}
if err := userCreate.Validate(); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid user create format").SetInternal(err)
}
if !usernameMatcher.MatchString(strings.ToLower(userCreate.Username)) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid username %s", userCreate.Username)).SetInternal(err)
}
// Disallow host user to be created.
if userCreate.Role == RoleHost {
return echo.NewHTTPError(http.StatusForbidden, "Could not create host user")
}
passwordHash, err := bcrypt.GenerateFromPassword([]byte(userCreate.Password), bcrypt.DefaultCost)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
}
user, err := s.Store.CreateUser(ctx, &store.User{
Username: userCreate.Username,
Role: store.Role(userCreate.Role),
Email: userCreate.Email,
Nickname: userCreate.Nickname,
PasswordHash: string(passwordHash),
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create user").SetInternal(err)
}
userMessage := convertUserFromStore(user)
metric.Enqueue("user create")
return c.JSON(http.StatusOK, userMessage)
}
// GetCurrentUser godoc
//
// @Summary Get current user
// @Tags user
// @Produce json
// @Success 200 {object} store.User "Current user"
// @Failure 401 {object} nil "Missing auth session"
// @Failure 500 {object} nil "Failed to find user | Failed to find userSettingList"
// @Router /api/v1/user/me [GET]
func (s *APIV1Service) GetCurrentUser(c echo.Context) error {
ctx := c.Request().Context()
userID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
}
user, err := s.Store.GetUser(ctx, &store.FindUser{ID: &userID})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if user == nil {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing auth session")
}
userMessage := convertUserFromStore(user)
return c.JSON(http.StatusOK, userMessage)
}
// GetUserByUsername godoc
//
// @Summary Get user by username
// @Tags user
// @Produce json
// @Param username path string true "Username"
// @Success 200 {object} store.User "Requested user"
// @Failure 404 {object} nil "User not found"
// @Failure 500 {object} nil "Failed to find user"
// @Router /api/v1/user/name/{username} [GET]
func (s *APIV1Service) GetUserByUsername(c echo.Context) error {
ctx := c.Request().Context()
username := c.Param("username")
user, err := s.Store.GetUser(ctx, &store.FindUser{Username: &username})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if user == nil {
return echo.NewHTTPError(http.StatusNotFound, "User not found")
}
userMessage := convertUserFromStore(user)
// data desensitize
userMessage.Email = ""
return c.JSON(http.StatusOK, userMessage)
}
// GetUserByID godoc
//
// @Summary Get user by id
// @Tags user
// @Produce json
// @Param id path int true "User ID"
// @Success 200 {object} store.User "Requested user"
// @Failure 400 {object} nil "Malformatted user id"
// @Failure 404 {object} nil "User not found"
// @Failure 500 {object} nil "Failed to find user"
// @Router /api/v1/user/{id} [GET]
func (s *APIV1Service) GetUserByID(c echo.Context) error {
ctx := c.Request().Context()
id, err := util.ConvertStringToInt32(c.Param("id"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted user id").SetInternal(err)
}
user, err := s.Store.GetUser(ctx, &store.FindUser{ID: &id})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if user == nil {
return echo.NewHTTPError(http.StatusNotFound, "User not found")
}
userMessage := convertUserFromStore(user)
userID, ok := c.Get(userIDContextKey).(int32)
if !ok || userID != user.ID {
// Data desensitize.
userMessage.Email = ""
}
return c.JSON(http.StatusOK, userMessage)
}
// DeleteUser godoc
//
// @Summary Delete a user
// @Tags user
// @Produce json
// @Param id path string true "User ID"
// @Success 200 {boolean} true "User deleted"
// @Failure 400 {object} nil "ID is not a number: %s | Current session user not found with ID: %d"
// @Failure 401 {object} nil "Missing user in session"
// @Failure 403 {object} nil "Unauthorized to delete user"
// @Failure 500 {object} nil "Failed to find user | Failed to delete user"
// @Router /api/v1/user/{id} [DELETE]
func (s *APIV1Service) DeleteUser(c echo.Context) error {
ctx := c.Request().Context()
currentUserID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
currentUser, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &currentUserID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if currentUser == nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Current session user not found with ID: %d", currentUserID)).SetInternal(err)
} else if currentUser.Role != store.RoleHost {
return echo.NewHTTPError(http.StatusForbidden, "Unauthorized to delete user").SetInternal(err)
}
userID, err := util.ConvertStringToInt32(c.Param("id"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("id"))).SetInternal(err)
}
userDelete := &store.DeleteUser{
ID: userID,
}
if err := s.Store.DeleteUser(ctx, userDelete); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to delete user").SetInternal(err)
}
return c.JSON(http.StatusOK, true)
}
// UpdateUser godoc
//
// @Summary Update a user
// @Tags user
// @Produce json
// @Param id path string true "User ID"
// @Param patch body UpdateUserRequest true "Patch request"
// @Success 200 {object} store.User "Updated user"
// @Failure 400 {object} nil "ID is not a number: %s | Current session user not found with ID: %d | Malformatted patch user request | Invalid update user request"
// @Failure 401 {object} nil "Missing user in session"
// @Failure 403 {object} nil "Unauthorized to update user"
// @Failure 500 {object} nil "Failed to find user | Failed to generate password hash | Failed to patch user | Failed to find userSettingList"
// @Router /api/v1/user/{id} [PATCH]
func (s *APIV1Service) UpdateUser(c echo.Context) error {
ctx := c.Request().Context()
userID, err := util.ConvertStringToInt32(c.Param("id"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.Param("id"))).SetInternal(err)
}
currentUserID, ok := c.Get(userIDContextKey).(int32)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
}
currentUser, err := s.Store.GetUser(ctx, &store.FindUser{ID: &currentUserID})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user").SetInternal(err)
}
if currentUser == nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Current session user not found with ID: %d", currentUserID)).SetInternal(err)
} else if currentUser.Role != store.RoleHost && currentUserID != userID {
return echo.NewHTTPError(http.StatusForbidden, "Unauthorized to update user").SetInternal(err)
}
request := &UpdateUserRequest{}
if err := json.NewDecoder(c.Request().Body).Decode(request); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch user request").SetInternal(err)
}
if err := request.Validate(); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid update user request").SetInternal(err)
}
currentTs := time.Now().Unix()
userUpdate := &store.UpdateUser{
ID: userID,
UpdatedTs: &currentTs,
}
if request.RowStatus != nil {
rowStatus := store.RowStatus(request.RowStatus.String())
userUpdate.RowStatus = &rowStatus
}
if request.Username != nil {
if !usernameMatcher.MatchString(strings.ToLower(*request.Username)) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid username %s", *request.Username)).SetInternal(err)
}
userUpdate.Username = request.Username
}
if request.Email != nil {
userUpdate.Email = request.Email
}
if request.Nickname != nil {
userUpdate.Nickname = request.Nickname
}
if request.Password != nil {
passwordHash, err := bcrypt.GenerateFromPassword([]byte(*request.Password), bcrypt.DefaultCost)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to generate password hash").SetInternal(err)
}
passwordHashStr := string(passwordHash)
userUpdate.PasswordHash = &passwordHashStr
}
if request.AvatarURL != nil {
userUpdate.AvatarURL = request.AvatarURL
}
user, err := s.Store.UpdateUser(ctx, userUpdate)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch user").SetInternal(err)
}
userMessage := convertUserFromStore(user)
return c.JSON(http.StatusOK, userMessage)
}
func (create CreateUserRequest) Validate() error {
if len(create.Username) < 3 {
return errors.New("username is too short, minimum length is 3")
}
if len(create.Username) > 32 {
return errors.New("username is too long, maximum length is 32")
}
if len(create.Password) < 3 {
return errors.New("password is too short, minimum length is 3")
}
if len(create.Password) > 512 {
return errors.New("password is too long, maximum length is 512")
}
if len(create.Nickname) > 64 {
return errors.New("nickname is too long, maximum length is 64")
}
if create.Email != "" {
if len(create.Email) > 256 {
return errors.New("email is too long, maximum length is 256")
}
if !util.ValidateEmail(create.Email) {
return errors.New("invalid email format")
}
}
return nil
}
func (update UpdateUserRequest) Validate() error {
if update.Username != nil && len(*update.Username) < 3 {
return errors.New("username is too short, minimum length is 3")
}
if update.Username != nil && len(*update.Username) > 32 {
return errors.New("username is too long, maximum length is 32")
}
if update.Password != nil && len(*update.Password) < 3 {
return errors.New("password is too short, minimum length is 3")
}
if update.Password != nil && len(*update.Password) > 512 {
return errors.New("password is too long, maximum length is 512")
}
if update.Nickname != nil && len(*update.Nickname) > 64 {
return errors.New("nickname is too long, maximum length is 64")
}
if update.AvatarURL != nil {
if len(*update.AvatarURL) > 2<<20 {
return errors.New("avatar is too large, maximum is 2MB")
}
}
if update.Email != nil && *update.Email != "" {
if len(*update.Email) > 256 {
return errors.New("email is too long, maximum length is 256")
}
if !util.ValidateEmail(*update.Email) {
return errors.New("invalid email format")
}
}
return nil
}
func convertUserFromStore(user *store.User) *User {
return &User{
ID: user.ID,
RowStatus: RowStatus(user.RowStatus),
CreatedTs: user.CreatedTs,
UpdatedTs: user.UpdatedTs,
Username: user.Username,
Role: Role(user.Role),
Email: user.Email,
Nickname: user.Nickname,
PasswordHash: user.PasswordHash,
AvatarURL: user.AvatarURL,
}
}
-94
View File
@@ -1,94 +0,0 @@
package v1
import (
"net/http"
"time"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/usememos/memos/api/resource"
"github.com/usememos/memos/plugin/telegram"
"github.com/usememos/memos/server/profile"
"github.com/usememos/memos/store"
)
type APIV1Service struct {
Secret string
Profile *profile.Profile
Store *store.Store
telegramBot *telegram.Bot
}
// @title memos API
// @version 1.0
// @description A privacy-first, lightweight note-taking service.
//
// @contact.name API Support
// @contact.url https://github.com/orgs/usememos/discussions
//
// @license.name MIT License
// @license.url https://github.com/usememos/memos/blob/main/LICENSE
//
// @BasePath /
//
// @externalDocs.url https://usememos.com/
// @externalDocs.description Find out more about Memos.
func NewAPIV1Service(secret string, profile *profile.Profile, store *store.Store, telegramBot *telegram.Bot) *APIV1Service {
return &APIV1Service{
Secret: secret,
Profile: profile,
Store: store,
telegramBot: telegramBot,
}
}
func (s *APIV1Service) Register(rootGroup *echo.Group) {
// Register RSS routes.
s.registerRSSRoutes(rootGroup)
// Register API v1 routes.
apiV1Group := rootGroup.Group("/api/v1")
apiV1Group.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
Store: middleware.NewRateLimiterMemoryStoreWithConfig(
middleware.RateLimiterMemoryStoreConfig{Rate: 30, Burst: 100, ExpiresIn: 3 * time.Minute},
),
IdentifierExtractor: func(ctx echo.Context) (string, error) {
id := ctx.RealIP()
return id, nil
},
ErrorHandler: func(context echo.Context, err error) error {
return context.JSON(http.StatusForbidden, nil)
},
DenyHandler: func(context echo.Context, identifier string, err error) error {
return context.JSON(http.StatusTooManyRequests, nil)
},
}))
apiV1Group.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return JWTMiddleware(s, next, s.Secret)
})
s.registerSystemRoutes(apiV1Group)
s.registerSystemSettingRoutes(apiV1Group)
s.registerAuthRoutes(apiV1Group)
s.registerIdentityProviderRoutes(apiV1Group)
s.registerUserRoutes(apiV1Group)
s.registerTagRoutes(apiV1Group)
s.registerStorageRoutes(apiV1Group)
s.registerResourceRoutes(apiV1Group)
s.registerMemoRoutes(apiV1Group)
s.registerMemoOrganizerRoutes(apiV1Group)
s.registerMemoRelationRoutes(apiV1Group)
// Register public routes.
publicGroup := rootGroup.Group("/o")
publicGroup.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return JWTMiddleware(s, next, s.Secret)
})
s.registerGetterPublicRoutes(publicGroup)
// Create and register resource public routes.
resourceService := resource.NewService(s.Profile, s.Store)
resourceService.RegisterResourcePublicRoutes(publicGroup)
// programmatically set API version same as the server version
SwaggerInfo.Version = s.Profile.Version
}
-27
View File
@@ -1,27 +0,0 @@
package v2
import "strings"
var authenticationAllowlistMethods = map[string]bool{
"/memos.api.v2.SystemService/GetSystemInfo": true,
"/memos.api.v2.AuthService/GetAuthStatus": true,
"/memos.api.v2.UserService/GetUser": true,
"/memos.api.v2.MemoService/ListMemos": true,
}
// isUnauthorizeAllowedMethod returns whether the method is exempted from authentication.
func isUnauthorizeAllowedMethod(fullMethodName string) bool {
if strings.HasPrefix(fullMethodName, "/grpc.reflection") {
return true
}
return authenticationAllowlistMethods[fullMethodName]
}
var allowedMethodsOnlyForAdmin = map[string]bool{
"/memos.api.v2.UserService/CreateUser": true,
}
// isOnlyForAdminAllowedMethod returns true if the method is allowed to be called only by admin.
func isOnlyForAdminAllowedMethod(methodName string) bool {
return allowedMethodsOnlyForAdmin[methodName]
}
-23
View File
@@ -1,23 +0,0 @@
package v2
import (
"context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
)
func (s *APIV2Service) GetAuthStatus(ctx context.Context, _ *apiv2pb.GetAuthStatusRequest) (*apiv2pb.GetAuthStatusResponse, error) {
user, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, "failed to get current user: %v", err)
}
if user == nil {
return nil, status.Errorf(codes.Unauthenticated, "user not found")
}
return &apiv2pb.GetAuthStatusResponse{
User: convertUserFromStore(user),
}, nil
}
-44
View File
@@ -1,44 +0,0 @@
package v2
import (
"context"
apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
"github.com/usememos/memos/store"
)
func convertRowStatusFromStore(rowStatus store.RowStatus) apiv2pb.RowStatus {
switch rowStatus {
case store.Normal:
return apiv2pb.RowStatus_ACTIVE
case store.Archived:
return apiv2pb.RowStatus_ARCHIVED
default:
return apiv2pb.RowStatus_ROW_STATUS_UNSPECIFIED
}
}
func convertRowStatusToStore(rowStatus apiv2pb.RowStatus) store.RowStatus {
switch rowStatus {
case apiv2pb.RowStatus_ACTIVE:
return store.Normal
case apiv2pb.RowStatus_ARCHIVED:
return store.Archived
default:
return store.Normal
}
}
func getCurrentUser(ctx context.Context, s *store.Store) (*store.User, error) {
username, ok := ctx.Value(usernameContextKey).(string)
if !ok {
return nil, nil
}
user, err := s.GetUser(ctx, &store.FindUser{
Username: &username,
})
if err != nil {
return nil, err
}
return user, nil
}
-138
View File
@@ -1,138 +0,0 @@
package v2
import (
"context"
"fmt"
"time"
"github.com/pkg/errors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
"github.com/usememos/memos/store"
)
func (s *APIV2Service) ListInboxes(ctx context.Context, _ *apiv2pb.ListInboxesRequest) (*apiv2pb.ListInboxesResponse, error) {
user, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user")
}
inboxes, err := s.Store.ListInboxes(ctx, &store.FindInbox{
ReceiverID: &user.ID,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list inbox: %v", err)
}
response := &apiv2pb.ListInboxesResponse{
Inboxes: []*apiv2pb.Inbox{},
}
for _, inbox := range inboxes {
inboxMessage, err := s.convertInboxFromStore(ctx, inbox)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to convert inbox from store: %v", err)
}
response.Inboxes = append(response.Inboxes, inboxMessage)
}
return response, nil
}
func (s *APIV2Service) UpdateInbox(ctx context.Context, request *apiv2pb.UpdateInboxRequest) (*apiv2pb.UpdateInboxResponse, error) {
if request.UpdateMask == nil || len(request.UpdateMask.Paths) == 0 {
return nil, status.Errorf(codes.InvalidArgument, "update mask is required")
}
inboxID, err := ExtractInboxIDFromName(request.Inbox.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid inbox name: %v", err)
}
update := &store.UpdateInbox{
ID: inboxID,
}
for _, field := range request.UpdateMask.Paths {
if field == "status" {
if request.Inbox.Status == apiv2pb.Inbox_STATUS_UNSPECIFIED {
return nil, status.Errorf(codes.InvalidArgument, "status is required")
}
update.Status = convertInboxStatusToStore(request.Inbox.Status)
}
}
inbox, err := s.Store.UpdateInbox(ctx, update)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to update inbox: %v", err)
}
inboxMessage, err := s.convertInboxFromStore(ctx, inbox)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to convert inbox from store: %v", err)
}
return &apiv2pb.UpdateInboxResponse{
Inbox: inboxMessage,
}, nil
}
func (s *APIV2Service) DeleteInbox(ctx context.Context, request *apiv2pb.DeleteInboxRequest) (*apiv2pb.DeleteInboxResponse, error) {
inboxID, err := ExtractInboxIDFromName(request.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid inbox name: %v", err)
}
if err := s.Store.DeleteInbox(ctx, &store.DeleteInbox{
ID: inboxID,
}); err != nil {
return nil, status.Errorf(codes.Internal, "failed to update inbox: %v", err)
}
return &apiv2pb.DeleteInboxResponse{}, nil
}
func (s *APIV2Service) convertInboxFromStore(ctx context.Context, inbox *store.Inbox) (*apiv2pb.Inbox, error) {
sender, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &inbox.SenderID,
})
if err != nil {
return nil, errors.Wrap(err, "failed to get sender")
}
receiver, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &inbox.ReceiverID,
})
if err != nil {
return nil, errors.Wrap(err, "failed to get receiver")
}
return &apiv2pb.Inbox{
Name: fmt.Sprintf("inboxes/%d", inbox.ID),
Sender: fmt.Sprintf("users/%s", sender.Username),
Receiver: fmt.Sprintf("users/%s", receiver.Username),
Status: convertInboxStatusFromStore(inbox.Status),
CreateTime: timestamppb.New(time.Unix(inbox.CreatedTs, 0)),
Type: apiv2pb.Inbox_Type(inbox.Message.Type),
ActivityId: inbox.Message.ActivityId,
}, nil
}
func convertInboxStatusFromStore(status store.InboxStatus) apiv2pb.Inbox_Status {
switch status {
case store.UNREAD:
return apiv2pb.Inbox_UNREAD
case store.ARCHIVED:
return apiv2pb.Inbox_ARCHIVED
default:
return apiv2pb.Inbox_STATUS_UNSPECIFIED
}
}
func convertInboxStatusToStore(status apiv2pb.Inbox_Status) store.InboxStatus {
switch status {
case apiv2pb.Inbox_UNREAD:
return store.UNREAD
case apiv2pb.Inbox_ARCHIVED:
return store.ARCHIVED
default:
return store.UNREAD
}
}
-266
View File
@@ -1,266 +0,0 @@
package v2
import (
"context"
"github.com/google/cel-go/cel"
"github.com/pkg/errors"
v1alpha1 "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
"github.com/usememos/memos/store"
)
func (s *APIV2Service) CreateMemo(ctx context.Context, request *apiv2pb.CreateMemoRequest) (*apiv2pb.CreateMemoResponse, error) {
user, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user")
}
if user == nil {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
create := &store.Memo{
CreatorID: user.ID,
Content: request.Content,
Visibility: store.Visibility(request.Visibility.String()),
}
memo, err := s.Store.CreateMemo(ctx, create)
if err != nil {
return nil, err
}
response := &apiv2pb.CreateMemoResponse{
Memo: convertMemoFromStore(memo),
}
return response, nil
}
func (s *APIV2Service) ListMemos(ctx context.Context, request *apiv2pb.ListMemosRequest) (*apiv2pb.ListMemosResponse, error) {
memoFind := &store.FindMemo{}
if request.Filter != "" {
filter, err := parseListMemosFilter(request.Filter)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid filter: %v", err)
}
if filter.Visibility != nil {
memoFind.VisibilityList = []store.Visibility{*filter.Visibility}
}
if filter.CreatedTsBefore != nil {
memoFind.CreatedTsBefore = filter.CreatedTsBefore
}
if filter.CreatedTsAfter != nil {
memoFind.CreatedTsAfter = filter.CreatedTsAfter
}
}
user, _ := getCurrentUser(ctx, s.Store)
// If the user is not authenticated, only public memos are visible.
if user == nil {
memoFind.VisibilityList = []store.Visibility{store.Public}
}
if request.CreatorId != nil {
memoFind.CreatorID = request.CreatorId
}
// Remove the private memos from the list if the user is not the creator.
if user != nil && request.CreatorId != nil && *request.CreatorId != user.ID {
var filteredVisibility []store.Visibility
for _, v := range memoFind.VisibilityList {
if v != store.Private {
filteredVisibility = append(filteredVisibility, v)
}
}
memoFind.VisibilityList = filteredVisibility
}
if request.PageSize != 0 {
offset := int(request.Page * request.PageSize)
limit := int(request.PageSize)
memoFind.Offset = &offset
memoFind.Limit = &limit
}
memos, err := s.Store.ListMemos(ctx, memoFind)
if err != nil {
return nil, err
}
memoMessages := make([]*apiv2pb.Memo, len(memos))
for i, memo := range memos {
memoMessages[i] = convertMemoFromStore(memo)
}
response := &apiv2pb.ListMemosResponse{
Memos: memoMessages,
}
return response, nil
}
func (s *APIV2Service) GetMemo(ctx context.Context, request *apiv2pb.GetMemoRequest) (*apiv2pb.GetMemoResponse, error) {
memo, err := s.Store.GetMemo(ctx, &store.FindMemo{
ID: &request.Id,
})
if err != nil {
return nil, err
}
if memo == nil {
return nil, status.Errorf(codes.NotFound, "memo not found")
}
if memo.Visibility != store.Public {
user, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user")
}
if user == nil {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
if memo.Visibility == store.Private && memo.CreatorID != user.ID {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
}
response := &apiv2pb.GetMemoResponse{
Memo: convertMemoFromStore(memo),
}
return response, nil
}
func (s *APIV2Service) CreateMemoComment(ctx context.Context, request *apiv2pb.CreateMemoCommentRequest) (*apiv2pb.CreateMemoCommentResponse, error) {
// Create the comment memo first.
createMemoResponse, err := s.CreateMemo(ctx, request.Create)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create memo")
}
// Build the relation between the comment memo and the original memo.
memo := createMemoResponse.Memo
_, err = s.Store.UpsertMemoRelation(ctx, &store.MemoRelation{
MemoID: memo.Id,
RelatedMemoID: request.Id,
Type: store.MemoRelationComment,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create memo relation")
}
response := &apiv2pb.CreateMemoCommentResponse{
Memo: memo,
}
return response, nil
}
func (s *APIV2Service) ListMemoComments(ctx context.Context, request *apiv2pb.ListMemoCommentsRequest) (*apiv2pb.ListMemoCommentsResponse, error) {
memoRelationComment := store.MemoRelationComment
memoRelations, err := s.Store.ListMemoRelations(ctx, &store.FindMemoRelation{
RelatedMemoID: &request.Id,
Type: &memoRelationComment,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list memo relations")
}
var memos []*apiv2pb.Memo
for _, memoRelation := range memoRelations {
memo, err := s.Store.GetMemo(ctx, &store.FindMemo{
ID: &memoRelation.MemoID,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get memo")
}
if memo != nil {
memos = append(memos, convertMemoFromStore(memo))
}
}
response := &apiv2pb.ListMemoCommentsResponse{
Memos: memos,
}
return response, nil
}
// ListMemosFilterCELAttributes are the CEL attributes for ListMemosFilter.
var ListMemosFilterCELAttributes = []cel.EnvOption{
cel.Variable("visibility", cel.StringType),
cel.Variable("created_ts_before", cel.IntType),
cel.Variable("created_ts_after", cel.IntType),
}
type ListMemosFilter struct {
Visibility *store.Visibility
CreatedTsBefore *int64
CreatedTsAfter *int64
}
func parseListMemosFilter(expression string) (*ListMemosFilter, error) {
e, err := cel.NewEnv(ListMemosFilterCELAttributes...)
if err != nil {
return nil, err
}
ast, issues := e.Compile(expression)
if issues != nil {
return nil, errors.Errorf("found issue %v", issues)
}
filter := &ListMemosFilter{}
expr, err := cel.AstToParsedExpr(ast)
if err != nil {
return nil, err
}
callExpr := expr.GetExpr().GetCallExpr()
findField(callExpr, filter)
return filter, nil
}
func findField(callExpr *v1alpha1.Expr_Call, filter *ListMemosFilter) {
if len(callExpr.Args) == 2 {
idExpr := callExpr.Args[0].GetIdentExpr()
if idExpr != nil {
if idExpr.Name == "visibility" {
visibility := store.Visibility(callExpr.Args[1].GetConstExpr().GetStringValue())
filter.Visibility = &visibility
}
if idExpr.Name == "created_ts_before" {
createdTsBefore := callExpr.Args[1].GetConstExpr().GetInt64Value()
filter.CreatedTsBefore = &createdTsBefore
}
if idExpr.Name == "created_ts_after" {
createdTsAfter := callExpr.Args[1].GetConstExpr().GetInt64Value()
filter.CreatedTsAfter = &createdTsAfter
}
return
}
}
for _, arg := range callExpr.Args {
callExpr := arg.GetCallExpr()
if callExpr != nil {
findField(callExpr, filter)
}
}
}
func convertMemoFromStore(memo *store.Memo) *apiv2pb.Memo {
return &apiv2pb.Memo{
Id: int32(memo.ID),
RowStatus: convertRowStatusFromStore(memo.RowStatus),
CreatedTs: memo.CreatedTs,
UpdatedTs: memo.UpdatedTs,
CreatorId: int32(memo.CreatorID),
Content: memo.Content,
Visibility: convertVisibilityFromStore(memo.Visibility),
Pinned: memo.Pinned,
}
}
func convertVisibilityFromStore(visibility store.Visibility) apiv2pb.Visibility {
switch visibility {
case store.Private:
return apiv2pb.Visibility_PRIVATE
case store.Protected:
return apiv2pb.Visibility_PROTECTED
case store.Public:
return apiv2pb.Visibility_PUBLIC
default:
return apiv2pb.Visibility_VISIBILITY_UNSPECIFIED
}
}
-57
View File
@@ -1,57 +0,0 @@
package v2
import (
"fmt"
"strings"
"github.com/pkg/errors"
"github.com/usememos/memos/internal/util"
)
const (
UserNamePrefix = "users/"
InboxNamePrefix = "inboxes/"
)
// GetNameParentTokens returns the tokens from a resource name.
func GetNameParentTokens(name string, tokenPrefixes ...string) ([]string, error) {
parts := strings.Split(name, "/")
if len(parts) != 2*len(tokenPrefixes) {
return nil, errors.Errorf("invalid request %q", name)
}
var tokens []string
for i, tokenPrefix := range tokenPrefixes {
if fmt.Sprintf("%s/", parts[2*i]) != tokenPrefix {
return nil, errors.Errorf("invalid prefix %q in request %q", tokenPrefix, name)
}
if parts[2*i+1] == "" {
return nil, errors.Errorf("invalid request %q with empty prefix %q", name, tokenPrefix)
}
tokens = append(tokens, parts[2*i+1])
}
return tokens, nil
}
// ExtractUsernameFromName returns the username from a resource name.
func ExtractUsernameFromName(name string) (string, error) {
tokens, err := GetNameParentTokens(name, UserNamePrefix)
if err != nil {
return "", err
}
return tokens[0], nil
}
// ExtractInboxIDFromName returns the inbox ID from a resource name.
func ExtractInboxIDFromName(name string) (int32, error) {
tokens, err := GetNameParentTokens(name, InboxNamePrefix)
if err != nil {
return 0, err
}
id, err := util.ConvertStringToInt32(tokens[0])
if err != nil {
return 0, errors.Errorf("invalid inbox ID %q", tokens[0])
}
return id, nil
}
-105
View File
@@ -1,105 +0,0 @@
package v2
import (
"context"
"time"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
"github.com/usememos/memos/store"
)
func (s *APIV2Service) ListResources(ctx context.Context, _ *apiv2pb.ListResourcesRequest) (*apiv2pb.ListResourcesResponse, error) {
user, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
}
resources, err := s.Store.ListResources(ctx, &store.FindResource{
CreatorID: &user.ID,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list resources: %v", err)
}
response := &apiv2pb.ListResourcesResponse{}
for _, resource := range resources {
response.Resources = append(response.Resources, s.convertResourceFromStore(ctx, resource))
}
return response, nil
}
func (s *APIV2Service) UpdateResource(ctx context.Context, request *apiv2pb.UpdateResourceRequest) (*apiv2pb.UpdateResourceResponse, error) {
if request.UpdateMask == nil || len(request.UpdateMask.Paths) == 0 {
return nil, status.Errorf(codes.InvalidArgument, "update mask is required")
}
currentTs := time.Now().Unix()
update := &store.UpdateResource{
ID: request.Resource.Id,
UpdatedTs: &currentTs,
}
for _, field := range request.UpdateMask.Paths {
if field == "filename" {
update.Filename = &request.Resource.Filename
} else if field == "memo_id" {
update.MemoID = request.Resource.MemoId
}
}
resource, err := s.Store.UpdateResource(ctx, update)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to update resource: %v", err)
}
return &apiv2pb.UpdateResourceResponse{
Resource: s.convertResourceFromStore(ctx, resource),
}, nil
}
func (s *APIV2Service) DeleteResource(ctx context.Context, request *apiv2pb.DeleteResourceRequest) (*apiv2pb.DeleteResourceResponse, error) {
user, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
}
resource, err := s.Store.GetResource(ctx, &store.FindResource{
ID: &request.Id,
CreatorID: &user.ID,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to find resource: %v", err)
}
if resource == nil {
return nil, status.Errorf(codes.NotFound, "resource not found")
}
// Delete the resource from the database.
if err := s.Store.DeleteResource(ctx, &store.DeleteResource{
ID: resource.ID,
}); err != nil {
return nil, status.Errorf(codes.Internal, "failed to delete resource: %v", err)
}
return &apiv2pb.DeleteResourceResponse{}, nil
}
func (s *APIV2Service) convertResourceFromStore(ctx context.Context, resource *store.Resource) *apiv2pb.Resource {
var memoID *int32
if resource.MemoID != nil {
memo, _ := s.Store.GetMemo(ctx, &store.FindMemo{
ID: resource.MemoID,
})
if memo != nil {
memoID = &memo.ID
}
}
return &apiv2pb.Resource{
Id: resource.ID,
CreatedTs: timestamppb.New(time.Unix(resource.CreatedTs, 0)),
Filename: resource.Filename,
ExternalLink: resource.ExternalLink,
Type: resource.Type,
Size: resource.Size,
MemoId: memoID,
}
}
-92
View File
@@ -1,92 +0,0 @@
package v2
import (
"context"
"strconv"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
"github.com/usememos/memos/store"
)
func (s *APIV2Service) GetSystemInfo(ctx context.Context, _ *apiv2pb.GetSystemInfoRequest) (*apiv2pb.GetSystemInfoResponse, error) {
defaultSystemInfo := &apiv2pb.SystemInfo{}
// Get the database size if the user is a host.
currentUser, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
}
if currentUser != nil && currentUser.Role == store.RoleHost {
size, err := s.Store.GetCurrentDBSize(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get db size: %v", err)
}
defaultSystemInfo.DbSize = size
}
response := &apiv2pb.GetSystemInfoResponse{
SystemInfo: defaultSystemInfo,
}
return response, nil
}
func (s *APIV2Service) UpdateSystemInfo(ctx context.Context, request *apiv2pb.UpdateSystemInfoRequest) (*apiv2pb.UpdateSystemInfoResponse, error) {
user, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
}
if user.Role != store.RoleHost {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
if request.UpdateMask == nil || len(request.UpdateMask.Paths) == 0 {
return nil, status.Errorf(codes.InvalidArgument, "update mask is required")
}
// Update system settings.
for _, field := range request.UpdateMask.Paths {
if field == "allow_registration" {
_, err := s.Store.UpsertSystemSetting(ctx, &store.SystemSetting{
Name: "allow-signup",
Value: strconv.FormatBool(request.SystemInfo.AllowRegistration),
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to update allow_registration system setting: %v", err)
}
} else if field == "disable_password_login" {
_, err := s.Store.UpsertSystemSetting(ctx, &store.SystemSetting{
Name: "disable-password-login",
Value: strconv.FormatBool(request.SystemInfo.DisablePasswordLogin),
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to update disable_password_login system setting: %v", err)
}
} else if field == "additional_script" {
_, err := s.Store.UpsertSystemSetting(ctx, &store.SystemSetting{
Name: "additional-script",
Value: request.SystemInfo.AdditionalScript,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to update additional_script system setting: %v", err)
}
} else if field == "additional_style" {
_, err := s.Store.UpsertSystemSetting(ctx, &store.SystemSetting{
Name: "additional-style",
Value: request.SystemInfo.AdditionalStyle,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to update additional_style system setting: %v", err)
}
}
}
systemInfo, err := s.GetSystemInfo(ctx, &apiv2pb.GetSystemInfoRequest{})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get system info: %v", err)
}
return &apiv2pb.UpdateSystemInfoResponse{
SystemInfo: systemInfo.SystemInfo,
}, nil
}
-181
View File
@@ -1,181 +0,0 @@
package v2
import (
"context"
"fmt"
"regexp"
"sort"
"github.com/pkg/errors"
"golang.org/x/exp/slices"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
"github.com/usememos/memos/store"
)
func (s *APIV2Service) UpsertTag(ctx context.Context, request *apiv2pb.UpsertTagRequest) (*apiv2pb.UpsertTagResponse, error) {
user, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user")
}
tag, err := s.Store.UpsertTag(ctx, &store.Tag{
Name: request.Name,
CreatorID: user.ID,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to upsert tag: %v", err)
}
t, err := s.convertTagFromStore(ctx, tag)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to convert tag: %v", err)
}
return &apiv2pb.UpsertTagResponse{
Tag: t,
}, nil
}
func (s *APIV2Service) ListTags(ctx context.Context, request *apiv2pb.ListTagsRequest) (*apiv2pb.ListTagsResponse, error) {
username, err := ExtractUsernameFromName(request.User)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid username: %v", err)
}
user, err := s.Store.GetUser(ctx, &store.FindUser{
Username: &username,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
if user == nil {
return nil, status.Errorf(codes.NotFound, "user not found")
}
tags, err := s.Store.ListTags(ctx, &store.FindTag{
CreatorID: user.ID,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list tags: %v", err)
}
response := &apiv2pb.ListTagsResponse{}
for _, tag := range tags {
t, err := s.convertTagFromStore(ctx, tag)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to convert tag: %v", err)
}
response.Tags = append(response.Tags, t)
}
return response, nil
}
func (s *APIV2Service) DeleteTag(ctx context.Context, request *apiv2pb.DeleteTagRequest) (*apiv2pb.DeleteTagResponse, error) {
username, err := ExtractUsernameFromName(request.Tag.Creator)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid username: %v", err)
}
user, err := s.Store.GetUser(ctx, &store.FindUser{
Username: &username,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
if user == nil {
return nil, status.Errorf(codes.NotFound, "user not found")
}
if err := s.Store.DeleteTag(ctx, &store.DeleteTag{
Name: request.Tag.Name,
CreatorID: user.ID,
}); err != nil {
return nil, status.Errorf(codes.Internal, "failed to delete tag: %v", err)
}
return &apiv2pb.DeleteTagResponse{}, nil
}
func (s *APIV2Service) GetTagSuggestions(ctx context.Context, request *apiv2pb.GetTagSuggestionsRequest) (*apiv2pb.GetTagSuggestionsResponse, error) {
username, err := ExtractUsernameFromName(request.User)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid username: %v", err)
}
user, err := s.Store.GetUser(ctx, &store.FindUser{
Username: &username,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
if user == nil {
return nil, status.Errorf(codes.NotFound, "user not found")
}
normalRowStatus := store.Normal
memoFind := &store.FindMemo{
CreatorID: &user.ID,
ContentSearch: []string{"#"},
RowStatus: &normalRowStatus,
}
memoList, err := s.Store.ListMemos(ctx, memoFind)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list memos: %v", err)
}
tagList, err := s.Store.ListTags(ctx, &store.FindTag{
CreatorID: user.ID,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list tags: %v", err)
}
tagNameList := []string{}
for _, tag := range tagList {
tagNameList = append(tagNameList, tag.Name)
}
tagMapSet := make(map[string]bool)
for _, memo := range memoList {
for _, tag := range findTagListFromMemoContent(memo.Content) {
if !slices.Contains(tagNameList, tag) {
tagMapSet[tag] = true
}
}
}
suggestions := []string{}
for tag := range tagMapSet {
suggestions = append(suggestions, tag)
}
sort.Strings(suggestions)
return &apiv2pb.GetTagSuggestionsResponse{
Tags: suggestions,
}, nil
}
func (s *APIV2Service) convertTagFromStore(ctx context.Context, tag *store.Tag) (*apiv2pb.Tag, error) {
user, err := s.Store.GetUser(ctx, &store.FindUser{
ID: &tag.CreatorID,
})
if err != nil {
return nil, errors.Wrap(err, "failed to get user")
}
return &apiv2pb.Tag{
Name: tag.Name,
Creator: fmt.Sprintf("%s%s", UserNamePrefix, user.Username),
}, nil
}
var tagRegexp = regexp.MustCompile(`#([^\s#,]+)`)
func findTagListFromMemoContent(memoContent string) []string {
tagMapSet := make(map[string]bool)
matches := tagRegexp.FindAllStringSubmatch(memoContent, -1)
for _, v := range matches {
tagName := v[1]
tagMapSet[tagName] = true
}
tagList := []string{}
for tag := range tagMapSet {
tagList = append(tagList, tag)
}
sort.Strings(tagList)
return tagList
}
-511
View File
@@ -1,511 +0,0 @@
package v2
import (
"context"
"fmt"
"net/http"
"regexp"
"strings"
"time"
"github.com/golang-jwt/jwt/v4"
"github.com/labstack/echo/v4"
"github.com/pkg/errors"
"golang.org/x/crypto/bcrypt"
"golang.org/x/exp/slices"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/usememos/memos/api/auth"
apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
)
var (
usernameMatcher = regexp.MustCompile("^[a-z0-9]([a-z0-9-]{1,30}[a-z0-9])$")
)
func (s *APIV2Service) GetUser(ctx context.Context, request *apiv2pb.GetUserRequest) (*apiv2pb.GetUserResponse, error) {
username, err := ExtractUsernameFromName(request.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "name is required")
}
user, err := s.Store.GetUser(ctx, &store.FindUser{
Username: &username,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
if user == nil {
return nil, status.Errorf(codes.NotFound, "user not found")
}
userMessage := convertUserFromStore(user)
response := &apiv2pb.GetUserResponse{
User: userMessage,
}
return response, nil
}
func (s *APIV2Service) CreateUser(ctx context.Context, request *apiv2pb.CreateUserRequest) (*apiv2pb.CreateUserResponse, error) {
currentUser, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
if currentUser.Role != store.RoleHost {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
username, err := ExtractUsernameFromName(request.User.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "name is required")
}
if !usernameMatcher.MatchString(strings.ToLower(username)) {
return nil, status.Errorf(codes.InvalidArgument, "invalid username: %s", username)
}
passwordHash, err := bcrypt.GenerateFromPassword([]byte(request.User.Password), bcrypt.DefaultCost)
if err != nil {
return nil, echo.NewHTTPError(http.StatusInternalServerError, "failed to generate password hash").SetInternal(err)
}
user, err := s.Store.CreateUser(ctx, &store.User{
Username: username,
Role: convertUserRoleToStore(request.User.Role),
Email: request.User.Email,
Nickname: request.User.Nickname,
PasswordHash: string(passwordHash),
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create user: %v", err)
}
response := &apiv2pb.CreateUserResponse{
User: convertUserFromStore(user),
}
return response, nil
}
func (s *APIV2Service) UpdateUser(ctx context.Context, request *apiv2pb.UpdateUserRequest) (*apiv2pb.UpdateUserResponse, error) {
username, err := ExtractUsernameFromName(request.User.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "name is required")
}
currentUser, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
if currentUser.Username != username && currentUser.Role != store.RoleAdmin && currentUser.Role != store.RoleHost {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
if request.UpdateMask == nil || len(request.UpdateMask.Paths) == 0 {
return nil, status.Errorf(codes.InvalidArgument, "update mask is empty")
}
user, err := s.Store.GetUser(ctx, &store.FindUser{Username: &username})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
if user == nil {
return nil, status.Errorf(codes.NotFound, "user not found")
}
currentTs := time.Now().Unix()
update := &store.UpdateUser{
ID: user.ID,
UpdatedTs: &currentTs,
}
for _, field := range request.UpdateMask.Paths {
if field == "username" {
if !usernameMatcher.MatchString(strings.ToLower(username)) {
return nil, status.Errorf(codes.InvalidArgument, "invalid username: %s", username)
}
update.Username = &username
} else if field == "nickname" {
update.Nickname = &request.User.Nickname
} else if field == "email" {
update.Email = &request.User.Email
} else if field == "avatar_url" {
update.AvatarURL = &request.User.AvatarUrl
} else if field == "role" {
role := convertUserRoleToStore(request.User.Role)
update.Role = &role
} else if field == "password" {
passwordHash, err := bcrypt.GenerateFromPassword([]byte(request.User.Password), bcrypt.DefaultCost)
if err != nil {
return nil, echo.NewHTTPError(http.StatusInternalServerError, "failed to generate password hash").SetInternal(err)
}
passwordHashStr := string(passwordHash)
update.PasswordHash = &passwordHashStr
} else if field == "row_status" {
rowStatus := convertRowStatusToStore(request.User.RowStatus)
update.RowStatus = &rowStatus
} else {
return nil, status.Errorf(codes.InvalidArgument, "invalid update path: %s", field)
}
}
updatedUser, err := s.Store.UpdateUser(ctx, update)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to update user: %v", err)
}
response := &apiv2pb.UpdateUserResponse{
User: convertUserFromStore(updatedUser),
}
return response, nil
}
func (s *APIV2Service) DeleteUser(ctx context.Context, request *apiv2pb.DeleteUserRequest) (*apiv2pb.DeleteUserResponse, error) {
username, err := ExtractUsernameFromName(request.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "name is required")
}
currentUser, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
if currentUser.Username != username && currentUser.Role != store.RoleAdmin && currentUser.Role != store.RoleHost {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
user, err := s.Store.GetUser(ctx, &store.FindUser{Username: &username})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
if user == nil {
return nil, status.Errorf(codes.NotFound, "user not found")
}
if err := s.Store.DeleteUser(ctx, &store.DeleteUser{
ID: user.ID,
}); err != nil {
return nil, status.Errorf(codes.Internal, "failed to delete user: %v", err)
}
return &apiv2pb.DeleteUserResponse{}, nil
}
func getDefaultUserSetting() *apiv2pb.UserSetting {
return &apiv2pb.UserSetting{
Locale: "en",
Appearance: "system",
MemoVisibility: "PRIVATE",
}
}
func (s *APIV2Service) GetUserSetting(ctx context.Context, _ *apiv2pb.GetUserSettingRequest) (*apiv2pb.GetUserSettingResponse, error) {
user, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
}
userSettings, err := s.Store.ListUserSettingsV1(ctx, &store.FindUserSetting{
UserID: &user.ID,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list user settings: %v", err)
}
userSettingMessage := getDefaultUserSetting()
for _, setting := range userSettings {
if setting.Key == storepb.UserSettingKey_USER_SETTING_LOCALE {
userSettingMessage.Locale = setting.GetLocale()
} else if setting.Key == storepb.UserSettingKey_USER_SETTING_APPEARANCE {
userSettingMessage.Appearance = setting.GetAppearance()
} else if setting.Key == storepb.UserSettingKey_USER_SETTING_MEMO_VISIBILITY {
userSettingMessage.MemoVisibility = setting.GetMemoVisibility()
} else if setting.Key == storepb.UserSettingKey_USER_SETTING_TELEGRAM_USER_ID {
userSettingMessage.TelegramUserId = setting.GetTelegramUserId()
}
}
return &apiv2pb.GetUserSettingResponse{
Setting: userSettingMessage,
}, nil
}
func (s *APIV2Service) UpdateUserSetting(ctx context.Context, request *apiv2pb.UpdateUserSettingRequest) (*apiv2pb.UpdateUserSettingResponse, error) {
user, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
}
if request.UpdateMask == nil || len(request.UpdateMask.Paths) == 0 {
return nil, status.Errorf(codes.InvalidArgument, "update mask is empty")
}
for _, field := range request.UpdateMask.Paths {
if field == "locale" {
if _, err := s.Store.UpsertUserSettingV1(ctx, &storepb.UserSetting{
UserId: user.ID,
Key: storepb.UserSettingKey_USER_SETTING_LOCALE,
Value: &storepb.UserSetting_Locale{
Locale: request.Setting.Locale,
},
}); err != nil {
return nil, status.Errorf(codes.Internal, "failed to upsert user setting: %v", err)
}
} else if field == "appearance" {
if _, err := s.Store.UpsertUserSettingV1(ctx, &storepb.UserSetting{
UserId: user.ID,
Key: storepb.UserSettingKey_USER_SETTING_APPEARANCE,
Value: &storepb.UserSetting_Appearance{
Appearance: request.Setting.Appearance,
},
}); err != nil {
return nil, status.Errorf(codes.Internal, "failed to upsert user setting: %v", err)
}
} else if field == "memo_visibility" {
if _, err := s.Store.UpsertUserSettingV1(ctx, &storepb.UserSetting{
UserId: user.ID,
Key: storepb.UserSettingKey_USER_SETTING_MEMO_VISIBILITY,
Value: &storepb.UserSetting_MemoVisibility{
MemoVisibility: request.Setting.MemoVisibility,
},
}); err != nil {
return nil, status.Errorf(codes.Internal, "failed to upsert user setting: %v", err)
}
} else if field == "telegram_user_id" {
if _, err := s.Store.UpsertUserSettingV1(ctx, &storepb.UserSetting{
UserId: user.ID,
Key: storepb.UserSettingKey_USER_SETTING_TELEGRAM_USER_ID,
Value: &storepb.UserSetting_TelegramUserId{
TelegramUserId: request.Setting.TelegramUserId,
},
}); err != nil {
return nil, status.Errorf(codes.Internal, "failed to upsert user setting: %v", err)
}
} else {
return nil, status.Errorf(codes.InvalidArgument, "invalid update path: %s", field)
}
}
userSettingResponse, err := s.GetUserSetting(ctx, &apiv2pb.GetUserSettingRequest{})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user setting: %v", err)
}
return &apiv2pb.UpdateUserSettingResponse{
Setting: userSettingResponse.Setting,
}, nil
}
func (s *APIV2Service) ListUserAccessTokens(ctx context.Context, request *apiv2pb.ListUserAccessTokensRequest) (*apiv2pb.ListUserAccessTokensResponse, error) {
user, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
}
if user == nil {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
userID := user.ID
username, err := ExtractUsernameFromName(request.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "name is required")
}
// List access token for other users need to be verified.
if user.Username != username {
// Normal users can only list their access tokens.
if user.Role == store.RoleUser {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
// The request user must be exist.
requestUser, err := s.Store.GetUser(ctx, &store.FindUser{Username: &username})
if requestUser == nil || err != nil {
return nil, status.Errorf(codes.NotFound, "fail to find user %s", username)
}
userID = requestUser.ID
}
userAccessTokens, err := s.Store.GetUserAccessTokens(ctx, userID)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list access tokens: %v", err)
}
accessTokens := []*apiv2pb.UserAccessToken{}
for _, userAccessToken := range userAccessTokens {
claims := &auth.ClaimsMessage{}
_, err := jwt.ParseWithClaims(userAccessToken.AccessToken, claims, func(t *jwt.Token) (any, error) {
if t.Method.Alg() != jwt.SigningMethodHS256.Name {
return nil, errors.Errorf("unexpected access token signing method=%v, expect %v", t.Header["alg"], jwt.SigningMethodHS256)
}
if kid, ok := t.Header["kid"].(string); ok {
if kid == "v1" {
return []byte(s.Secret), nil
}
}
return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"])
})
if err != nil {
// If the access token is invalid or expired, just ignore it.
continue
}
userAccessToken := &apiv2pb.UserAccessToken{
AccessToken: userAccessToken.AccessToken,
Description: userAccessToken.Description,
IssuedAt: timestamppb.New(claims.IssuedAt.Time),
}
if claims.ExpiresAt != nil {
userAccessToken.ExpiresAt = timestamppb.New(claims.ExpiresAt.Time)
}
accessTokens = append(accessTokens, userAccessToken)
}
// Sort by issued time in descending order.
slices.SortFunc(accessTokens, func(i, j *apiv2pb.UserAccessToken) int {
return int(i.IssuedAt.Seconds - j.IssuedAt.Seconds)
})
response := &apiv2pb.ListUserAccessTokensResponse{
AccessTokens: accessTokens,
}
return response, nil
}
func (s *APIV2Service) CreateUserAccessToken(ctx context.Context, request *apiv2pb.CreateUserAccessTokenRequest) (*apiv2pb.CreateUserAccessTokenResponse, error) {
user, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
}
expiresAt := time.Time{}
if request.ExpiresAt != nil {
expiresAt = request.ExpiresAt.AsTime()
}
accessToken, err := auth.GenerateAccessToken(user.Username, user.ID, expiresAt, []byte(s.Secret))
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to generate access token: %v", err)
}
claims := &auth.ClaimsMessage{}
_, err = jwt.ParseWithClaims(accessToken, claims, func(t *jwt.Token) (any, error) {
if t.Method.Alg() != jwt.SigningMethodHS256.Name {
return nil, errors.Errorf("unexpected access token signing method=%v, expect %v", t.Header["alg"], jwt.SigningMethodHS256)
}
if kid, ok := t.Header["kid"].(string); ok {
if kid == "v1" {
return []byte(s.Secret), nil
}
}
return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"])
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to parse access token: %v", err)
}
// Upsert the access token to user setting store.
if err := s.UpsertAccessTokenToStore(ctx, user, accessToken, request.Description); err != nil {
return nil, status.Errorf(codes.Internal, "failed to upsert access token to store: %v", err)
}
userAccessToken := &apiv2pb.UserAccessToken{
AccessToken: accessToken,
Description: request.Description,
IssuedAt: timestamppb.New(claims.IssuedAt.Time),
}
if claims.ExpiresAt != nil {
userAccessToken.ExpiresAt = timestamppb.New(claims.ExpiresAt.Time)
}
response := &apiv2pb.CreateUserAccessTokenResponse{
AccessToken: userAccessToken,
}
return response, nil
}
func (s *APIV2Service) DeleteUserAccessToken(ctx context.Context, request *apiv2pb.DeleteUserAccessTokenRequest) (*apiv2pb.DeleteUserAccessTokenResponse, error) {
user, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
}
userAccessTokens, err := s.Store.GetUserAccessTokens(ctx, user.ID)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list access tokens: %v", err)
}
updatedUserAccessTokens := []*storepb.AccessTokensUserSetting_AccessToken{}
for _, userAccessToken := range userAccessTokens {
if userAccessToken.AccessToken == request.AccessToken {
continue
}
updatedUserAccessTokens = append(updatedUserAccessTokens, userAccessToken)
}
if _, err := s.Store.UpsertUserSettingV1(ctx, &storepb.UserSetting{
UserId: user.ID,
Key: storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS,
Value: &storepb.UserSetting_AccessTokens{
AccessTokens: &storepb.AccessTokensUserSetting{
AccessTokens: updatedUserAccessTokens,
},
},
}); err != nil {
return nil, status.Errorf(codes.Internal, "failed to upsert user setting: %v", err)
}
return &apiv2pb.DeleteUserAccessTokenResponse{}, nil
}
func (s *APIV2Service) UpsertAccessTokenToStore(ctx context.Context, user *store.User, accessToken, description string) error {
userAccessTokens, err := s.Store.GetUserAccessTokens(ctx, user.ID)
if err != nil {
return errors.Wrap(err, "failed to get user access tokens")
}
userAccessToken := storepb.AccessTokensUserSetting_AccessToken{
AccessToken: accessToken,
Description: description,
}
userAccessTokens = append(userAccessTokens, &userAccessToken)
if _, err := s.Store.UpsertUserSettingV1(ctx, &storepb.UserSetting{
UserId: user.ID,
Key: storepb.UserSettingKey_USER_SETTING_ACCESS_TOKENS,
Value: &storepb.UserSetting_AccessTokens{
AccessTokens: &storepb.AccessTokensUserSetting{
AccessTokens: userAccessTokens,
},
},
}); err != nil {
return errors.Wrap(err, "failed to upsert user setting")
}
return nil
}
func convertUserFromStore(user *store.User) *apiv2pb.User {
return &apiv2pb.User{
Name: fmt.Sprintf("%s%s", UserNamePrefix, user.Username),
Id: user.ID,
RowStatus: convertRowStatusFromStore(user.RowStatus),
CreateTime: timestamppb.New(time.Unix(user.CreatedTs, 0)),
UpdateTime: timestamppb.New(time.Unix(user.UpdatedTs, 0)),
Role: convertUserRoleFromStore(user.Role),
Email: user.Email,
Nickname: user.Nickname,
AvatarUrl: user.AvatarURL,
}
}
func convertUserRoleFromStore(role store.Role) apiv2pb.User_Role {
switch role {
case store.RoleHost:
return apiv2pb.User_HOST
case store.RoleAdmin:
return apiv2pb.User_ADMIN
case store.RoleUser:
return apiv2pb.User_USER
default:
return apiv2pb.User_ROLE_UNSPECIFIED
}
}
func convertUserRoleToStore(role apiv2pb.User_Role) store.Role {
switch role {
case apiv2pb.User_HOST:
return store.RoleHost
case apiv2pb.User_ADMIN:
return store.RoleAdmin
case apiv2pb.User_USER:
return store.RoleUser
default:
return store.RoleUser
}
}
-126
View File
@@ -1,126 +0,0 @@
package v2
import (
"context"
"fmt"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/improbable-eng/grpc-web/go/grpcweb"
"github.com/labstack/echo/v4"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/reflection"
apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
"github.com/usememos/memos/server/profile"
"github.com/usememos/memos/store"
)
type APIV2Service struct {
apiv2pb.UnimplementedSystemServiceServer
apiv2pb.UnimplementedAuthServiceServer
apiv2pb.UnimplementedUserServiceServer
apiv2pb.UnimplementedMemoServiceServer
apiv2pb.UnimplementedResourceServiceServer
apiv2pb.UnimplementedTagServiceServer
apiv2pb.UnimplementedInboxServiceServer
apiv2pb.UnimplementedActivityServiceServer
apiv2pb.UnimplementedWebhookServiceServer
Secret string
Profile *profile.Profile
Store *store.Store
grpcServer *grpc.Server
grpcServerPort int
}
func NewAPIV2Service(secret string, profile *profile.Profile, store *store.Store, grpcServerPort int) *APIV2Service {
grpc.EnableTracing = true
authProvider := NewGRPCAuthInterceptor(store, secret)
grpcServer := grpc.NewServer(
grpc.ChainUnaryInterceptor(
authProvider.AuthenticationInterceptor,
),
)
apiv2Service := &APIV2Service{
Secret: secret,
Profile: profile,
Store: store,
grpcServer: grpcServer,
grpcServerPort: grpcServerPort,
}
apiv2pb.RegisterSystemServiceServer(grpcServer, apiv2Service)
apiv2pb.RegisterAuthServiceServer(grpcServer, apiv2Service)
apiv2pb.RegisterUserServiceServer(grpcServer, apiv2Service)
apiv2pb.RegisterMemoServiceServer(grpcServer, apiv2Service)
apiv2pb.RegisterTagServiceServer(grpcServer, apiv2Service)
apiv2pb.RegisterResourceServiceServer(grpcServer, apiv2Service)
apiv2pb.RegisterInboxServiceServer(grpcServer, apiv2Service)
apiv2pb.RegisterActivityServiceServer(grpcServer, apiv2Service)
apiv2pb.RegisterWebhookServiceServer(grpcServer, apiv2Service)
reflection.Register(grpcServer)
return apiv2Service
}
func (s *APIV2Service) GetGRPCServer() *grpc.Server {
return s.grpcServer
}
// RegisterGateway registers the gRPC-Gateway with the given Echo instance.
func (s *APIV2Service) RegisterGateway(ctx context.Context, e *echo.Echo) error {
// Create a client connection to the gRPC Server we just started.
// This is where the gRPC-Gateway proxies the requests.
conn, err := grpc.DialContext(
ctx,
fmt.Sprintf(":%d", s.grpcServerPort),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
return err
}
gwMux := runtime.NewServeMux()
if err := apiv2pb.RegisterSystemServiceHandler(context.Background(), gwMux, conn); err != nil {
return err
}
if err := apiv2pb.RegisterAuthServiceHandler(context.Background(), gwMux, conn); err != nil {
return err
}
if err := apiv2pb.RegisterUserServiceHandler(context.Background(), gwMux, conn); err != nil {
return err
}
if err := apiv2pb.RegisterMemoServiceHandler(context.Background(), gwMux, conn); err != nil {
return err
}
if err := apiv2pb.RegisterTagServiceHandler(context.Background(), gwMux, conn); err != nil {
return err
}
if err := apiv2pb.RegisterResourceServiceHandler(context.Background(), gwMux, conn); err != nil {
return err
}
if err := apiv2pb.RegisterInboxServiceHandler(context.Background(), gwMux, conn); err != nil {
return err
}
if err := apiv2pb.RegisterActivityServiceHandler(context.Background(), gwMux, conn); err != nil {
return err
}
if err := apiv2pb.RegisterWebhookServiceHandler(context.Background(), gwMux, conn); err != nil {
return err
}
e.Any("/api/v2/*", echo.WrapHandler(gwMux))
// GRPC web proxy.
options := []grpcweb.Option{
grpcweb.WithCorsForRegisteredEndpointsOnly(false),
grpcweb.WithOriginFunc(func(origin string) bool {
return true
}),
}
wrappedGrpc := grpcweb.WrapServer(s.grpcServer, options...)
e.Any("/memos.api.v2.*", echo.WrapHandler(wrappedGrpc))
return nil
}
-120
View File
@@ -1,120 +0,0 @@
package v2
import (
"context"
"time"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
)
func (s *APIV2Service) CreateWebhook(ctx context.Context, request *apiv2pb.CreateWebhookRequest) (*apiv2pb.CreateWebhookResponse, error) {
currentUser, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
webhook, err := s.Store.CreateWebhook(ctx, &storepb.Webhook{
CreatorId: currentUser.ID,
Name: request.Name,
Url: request.Url,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create webhook, error: %+v", err)
}
return &apiv2pb.CreateWebhookResponse{
Webhook: convertWebhookFromStore(webhook),
}, nil
}
func (s *APIV2Service) ListWebhooks(ctx context.Context, request *apiv2pb.ListWebhooksRequest) (*apiv2pb.ListWebhooksResponse, error) {
webhooks, err := s.Store.ListWebhooks(ctx, &store.FindWebhook{
CreatorID: &request.CreatorId,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list webhooks, error: %+v", err)
}
response := &apiv2pb.ListWebhooksResponse{
Webhooks: []*apiv2pb.Webhook{},
}
for _, webhook := range webhooks {
response.Webhooks = append(response.Webhooks, convertWebhookFromStore(webhook))
}
return response, nil
}
func (s *APIV2Service) GetWebhook(ctx context.Context, request *apiv2pb.GetWebhookRequest) (*apiv2pb.GetWebhookResponse, error) {
currentUser, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
}
webhook, err := s.Store.GetWebhooks(ctx, &store.FindWebhook{
ID: &request.Id,
CreatorID: &currentUser.ID,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get webhook, error: %+v", err)
}
if webhook == nil {
return nil, status.Errorf(codes.NotFound, "webhook not found")
}
return &apiv2pb.GetWebhookResponse{
Webhook: convertWebhookFromStore(webhook),
}, nil
}
func (s *APIV2Service) UpdateWebhook(ctx context.Context, request *apiv2pb.UpdateWebhookRequest) (*apiv2pb.UpdateWebhookResponse, error) {
if request.UpdateMask == nil || len(request.UpdateMask.Paths) == 0 {
return nil, status.Errorf(codes.InvalidArgument, "update_mask is required")
}
update := &store.UpdateWebhook{}
for _, field := range request.UpdateMask.Paths {
switch field {
case "row_status":
rowStatus := storepb.RowStatus(storepb.RowStatus_value[request.Webhook.RowStatus.String()])
update.RowStatus = &rowStatus
case "name":
update.Name = &request.Webhook.Name
case "url":
update.URL = &request.Webhook.Url
}
}
webhook, err := s.Store.UpdateWebhook(ctx, update)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to update webhook, error: %+v", err)
}
return &apiv2pb.UpdateWebhookResponse{
Webhook: convertWebhookFromStore(webhook),
}, nil
}
func (s *APIV2Service) DeleteWebhook(ctx context.Context, request *apiv2pb.DeleteWebhookRequest) (*apiv2pb.DeleteWebhookResponse, error) {
err := s.Store.DeleteWebhook(ctx, &store.DeleteWebhook{
ID: request.Id,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to delete webhook, error: %+v", err)
}
return &apiv2pb.DeleteWebhookResponse{}, nil
}
func convertWebhookFromStore(webhook *storepb.Webhook) *apiv2pb.Webhook {
return &apiv2pb.Webhook{
Id: webhook.Id,
CreatedTime: timestamppb.New(time.Unix(webhook.CreatedTs, 0)),
UpdatedTime: timestamppb.New(time.Unix(webhook.UpdatedTs, 0)),
RowStatus: apiv2pb.RowStatus(webhook.RowStatus),
CreatorId: webhook.CreatorId,
Name: webhook.Name,
Url: webhook.Url,
}
}
+58 -59
View File
@@ -3,6 +3,7 @@ package main
import (
"context"
"fmt"
"log/slog"
"net/http"
"os"
"os/signal"
@@ -10,12 +11,9 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.uber.org/zap"
"github.com/usememos/memos/internal/log"
"github.com/usememos/memos/server"
_profile "github.com/usememos/memos/server/profile"
"github.com/usememos/memos/server/service/metric"
"github.com/usememos/memos/server/profile"
"github.com/usememos/memos/store"
"github.com/usememos/memos/store/db"
)
@@ -32,66 +30,67 @@ const (
)
var (
profile *_profile.Profile
mode string
addr string
port int
data string
driver string
dsn string
enableMetric bool
mode string
addr string
port int
data string
driver string
dsn string
public bool
instanceProfile *profile.Profile
rootCmd = &cobra.Command{
Use: "memos",
Short: `An open-source, self-hosted memo hub with knowledge management and social networking.`,
Run: func(_cmd *cobra.Command, _args []string) {
Short: `An open source, lightweight note-taking service. Easily capture and share your great thoughts.`,
Run: func(_ *cobra.Command, _ []string) {
ctx, cancel := context.WithCancel(context.Background())
dbDriver, err := db.NewDBDriver(profile)
dbDriver, err := db.NewDBDriver(instanceProfile)
if err != nil {
cancel()
log.Error("failed to create db driver", zap.Error(err))
slog.Error("failed to create db driver", err)
return
}
if err := dbDriver.Migrate(ctx); err != nil {
cancel()
log.Error("failed to migrate db", zap.Error(err))
slog.Error("failed to migrate database", err)
return
}
store := store.New(dbDriver, profile)
s, err := server.NewServer(ctx, profile, store)
storeInstance := store.New(dbDriver, instanceProfile)
if err := storeInstance.MigrateManually(ctx); err != nil {
cancel()
slog.Error("failed to migrate manually", err)
return
}
s, err := server.NewServer(ctx, instanceProfile, storeInstance)
if err != nil {
cancel()
log.Error("failed to create server", zap.Error(err))
slog.Error("failed to create server", err)
return
}
if profile.Metric {
// nolint
metric.NewMetricClient(s.ID, *profile)
}
c := make(chan os.Signal, 1)
// Trigger graceful shutdown on SIGINT or SIGTERM.
// The default signal sent by the `kill` command is SIGTERM,
// which is taken as the graceful shutdown signal for many systems, eg., Kubernetes, Gunicorn.
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
sig := <-c
log.Info(fmt.Sprintf("%s received.\n", sig.String()))
s.Shutdown(ctx)
cancel()
}()
printGreetings()
if err := s.Start(ctx); err != nil {
if err != http.ErrServerClosed {
log.Error("failed to start server", zap.Error(err))
slog.Error("failed to start server", err)
cancel()
}
}
printGreetings()
go func() {
<-c
s.Shutdown(ctx)
cancel()
}()
// Wait for CTRL-C.
<-ctx.Done()
},
@@ -99,7 +98,6 @@ var (
)
func Execute() error {
defer log.Sync()
return rootCmd.Execute()
}
@@ -112,7 +110,7 @@ func init() {
rootCmd.PersistentFlags().StringVarP(&data, "data", "d", "", "data directory")
rootCmd.PersistentFlags().StringVarP(&driver, "driver", "", "", "database driver")
rootCmd.PersistentFlags().StringVarP(&dsn, "dsn", "", "", "database source name(aka. DSN)")
rootCmd.PersistentFlags().BoolVarP(&enableMetric, "metric", "", true, "allow metric collection")
rootCmd.PersistentFlags().BoolVarP(&public, "public", "", true, "")
err := viper.BindPFlag("mode", rootCmd.PersistentFlags().Lookup("mode"))
if err != nil {
@@ -138,7 +136,7 @@ func init() {
if err != nil {
panic(err)
}
err = viper.BindPFlag("metric", rootCmd.PersistentFlags().Lookup("metric"))
err = viper.BindPFlag("public", rootCmd.PersistentFlags().Lookup("public"))
if err != nil {
panic(err)
}
@@ -147,44 +145,45 @@ func init() {
viper.SetDefault("driver", "sqlite")
viper.SetDefault("addr", "")
viper.SetDefault("port", 8081)
viper.SetDefault("metric", true)
viper.SetDefault("public", true)
viper.SetEnvPrefix("memos")
}
func initConfig() {
viper.AutomaticEnv()
var err error
profile, err = _profile.GetProfile()
instanceProfile, err = profile.GetProfile()
if err != nil {
fmt.Printf("failed to get profile, error: %+v\n", err)
slog.Error("failed to get profile", err)
return
}
println("---")
println("Server profile")
println("data:", profile.Data)
println("dsn:", profile.DSN)
println("addr:", profile.Addr)
println("port:", profile.Port)
println("mode:", profile.Mode)
println("driver:", profile.Driver)
println("version:", profile.Version)
println("metric:", profile.Metric)
println("---")
fmt.Printf(`---
Server profile
version: %s
data: %s
dsn: %s
addr: %s
port: %d
mode: %s
driver: %s
---
`, instanceProfile.Version, instanceProfile.Data, instanceProfile.DSN, instanceProfile.Addr, instanceProfile.Port, instanceProfile.Mode, instanceProfile.Driver)
}
func printGreetings() {
print(greetingBanner)
if len(profile.Addr) == 0 {
fmt.Printf("Version %s has been started on port %d\n", profile.Version, profile.Port)
if len(instanceProfile.Addr) == 0 {
fmt.Printf("Version %s has been started on port %d\n", instanceProfile.Version, instanceProfile.Port)
} else {
fmt.Printf("Version %s has been started on address '%s' and port %d\n", profile.Version, profile.Addr, profile.Port)
fmt.Printf("Version %s has been started on address '%s' and port %d\n", instanceProfile.Version, instanceProfile.Addr, instanceProfile.Port)
}
println("---")
println("See more in:")
fmt.Printf("👉Website: %s\n", "https://usememos.com")
fmt.Printf("👉GitHub: %s\n", "https://github.com/usememos/memos")
println("---")
fmt.Printf(`---
See more in:
👉Website: %s
👉GitHub: %s
---
`, "https://usememos.com", "https://github.com/usememos/memos")
}
func main() {
-70
View File
@@ -1,70 +0,0 @@
services:
db:
image: mysql
volumes:
- ./.air/mysql:/var/lib/mysql
api:
image: cosmtrek/air
working_dir: /work
command: ["-c", "./scripts/.air.toml"]
environment:
- "MEMOS_DSN=root@tcp(db)/memos"
- "MEMOS_DRIVER=mysql"
volumes:
- .:/work/
- .air/go-build:/root/.cache/go-build
- $HOME/go/pkg/:/go/pkg/ # Cache for go mod shared with the host
web:
image: node:20-alpine
working_dir: /work
depends_on: ["api"]
ports: ["3001:3001"]
environment: ["DEV_PROXY_SERVER=http://api:8081/"]
entrypoint: ["/bin/sh", "-c"]
command: ["corepack enable && pnpm install && pnpm dev"]
volumes:
- ./web:/work
- ./.air/node_modules/:/work/node_modules/ # Cache for Node Modules
# Services below are used for developers to run once
#
# You can just run `docker compose run --rm SERVICE_NAME` to use
# For example:
# To regenerate typescript code of gRPC proto
# Just run `docker compose run --rm buf`
#
# All of theses services belongs to profile 'tools'
# This will prevent to launch by normally `docker compose up` unexpectly
# Generate typescript code of gRPC proto
buf:
profiles: ["tools"]
image: bufbuild/buf
working_dir: /work/proto
command: generate
volumes:
- ./proto:/work/proto
- ./web/src/types/:/work/web/src/types/
# Do golang static code check before create PR
golangci-lint:
profiles: ["tools"]
image: golangci/golangci-lint:v1.54.2
working_dir: /work/
entrypoint: golangci-lint
command: run -v
volumes:
- $HOME/go/pkg/:/go/pkg/ # Cache for go mod shared with the host
- .air/go-build:/root/.cache/go-build
- .:/work/
# run npm
npm:
profiles: ["tools"]
image: node:20-alpine
working_dir: /work
environment: ["NPM_CONFIG_UPDATE_NOTIFIER=false"]
entrypoint: "npm"
volumes:
- ./web:/work
- ./.air/node_modules/:/work/node_modules/
-1607
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -56,7 +56,7 @@ Memos should now be running at [http://localhost:3001](http://localhost:3001) an
## Building
Frontend must be built before backend. The built frontend must be placed in the backend ./server/frontend/dist directory. Otherwise, you will get a "No frontend embeded" error.
Frontend must be built before backend. The built frontend must be placed in the backend ./server/frontend/dist directory. Otherwise, you will get a "No frontend embedded" error.
### Frontend
+2 -2
View File
@@ -21,7 +21,7 @@ Memos is built with a curated tech stack. It is optimized for developer experien
git clone https://github.com/usememos/memos
```
2. Start backend server with `air`(with live reload)
2. Start backend server with [`air`](https://github.com/cosmtrek/air) (with live reload)
```bash
air -c scripts/.air.toml
@@ -30,7 +30,7 @@ Memos is built with a curated tech stack. It is optimized for developer experien
3. Install frontend dependencies and generate TypeScript code from protobuf
```
cd web && pnpm i && pnpm type-gen
cd web && pnpm i
```
4. Start the dev server of frontend
-113
View File
@@ -1,113 +0,0 @@
# Documenting the API
## Principles
- The documentation is generated by [swaggo/swag](https://github.com/swaggo/swag) from comments in the API code.
- Documentation is written using [Declarative Comments Format](https://github.com/swaggo/swag#declarative-comments-format).
- The documentation is generated in the `./api/v1` folder as `docs.go`.
- [echo-swagger](https://github.com/swaggo/echo-swagger) is used to integrate with Echo framework and serve the documentation with [Swagger-UI](https://swagger.io/tools/swagger-ui/) at `http://memos.host:5230/api/index.html`
## Updating the documentation
1. Update or add API-related comments in the code. Make sure to follow the [Declarative Comments Format](https://github.com/swaggo/swag#declarative-comments-format):
```go
// signIn godoc
//
// @Summary Sign-in to memos.
// @Tags auth
// @Accept json
// @Produce json
// @Param body body SignIn true "Sign-in object"
// @Success 200 {object} store.User "User information"
// @Failure 400 {object} nil "Malformatted signin request"
// @Failure 401 {object} nil "Password login is deactivated | Incorrect login credentials, please try again"
// @Failure 403 {object} nil "User has been archived with username {username}"
// @Failure 500 {object} nil "Failed to find system setting | Failed to unmarshal system setting | Incorrect login credentials, please try again | Failed to generate tokens | Failed to create activity"
// @Router /api/v1/auth/signin [POST]
func (s *APIV1Service) signIn(c echo.Context) error {
...
```
> Sample from [api/v1/auth.go](https://github.com/usememos/memos/tree/main/api/v1/auth.go)
> You can check existing comments at [api/v1](https://github.com/usememos/memos/tree/main/api/v1)
2. Run one of the following provided scripts:
- Linux: `./scripts/gen-api-v1-docs.sh` (remember to `chmod +x` the script first)
- Windows: `./scripts/gen-api-v1-docs.ps1`
> The scripts will install swag if needed (via go install), then run `swag fmt` and `swag init` commands.
3. That's it! The documentation is updated. You can check it at `http://memos.host:5230/api/index.html`
### Extra tips
- If you reference a custom Go struct from outside the API file, use a relative definition, like `store.IdentityProvider`. This works because `./` is passed to swag at `--dir` argument. If swag can't resolve the reference, it will fail.
- If the API grows or you need to reference some type from another location, remember to update ./scripts/gen-api-v1-docs.cfg file with the new paths.
- It's possible to list multiple errors for the same code using enum-like structs, that will show a proper, spec-conformant model with all entries at Swagger-UI. The drawback is that this approach requires a major refactoring and will add a lot of boilerplate code, as there are inconsistencies between API methods error responses.
```go
type signInInternalServerError string
const signInErrorFailedToFindSystemSetting signInInternalServerError = "Failed to find system setting"
const signInErrorFailedToUnmarshalSystemSetting signInInternalServerError = "Failed to unmarshal system setting"
const signInErrorIncorrectLoginCredentials signInInternalServerError = "Incorrect login credentials, please try again"
const signInErrorFailedToGenerateTokens signInInternalServerError = "Failed to generate tokens"
const signInErrorFailedToCreateActivity signInInternalServerError = "Failed to create activity"
type signInUnauthorized string
const signInErrorPasswordLoginDeactivated signInUnauthorized = "Password login is deactivated"
const signInErrorIncorrectCredentials signInUnauthorized = "Incorrect login credentials, please try again"
// signIn godoc
//
// @Summary Sign-in to memos.
// @Tags auth
// @Accept json
// @Produce json
// @Param body body SignIn true "Sign-in object"
// @Success 200 {object} store.User "User information"
// @Failure 400 {object} nil "Malformatted signin request"
// @Failure 401 {object} signInUnauthorized
// @Failure 403 {object} nil "User has been archived with username {username}"
// @Failure 500 {object} signInInternalServerError
// @Router /api/v1/auth/signin [POST]
func (s *APIV1Service) signIn(c echo.Context) error {
...
```
### Step-by-step (no scripts)
#### Required tools
```bash
# Swag v1.8.12 or newer
# Also updates swag if needed
$ go install github.com/swaggo/swag/cmd/swag@latest
```
If `$HOME/go/bin` is not in your `PATH`, you can call `swag` directly at `$HOME/go/bin/swag`.
#### Generate the documentation
1. Run `swag fmt` to format the comments
```bash
swag fmt --dir ./api/v1 && go fmt
```
2. Run `swag init` to generate the documentation
```bash
cd <project-root>
swag init --output ./api/v1 --generalInfo ./api/v1/v1.go --dir ./,./api/v1
```
> If the API gets a new version, you'll need to add the file system path to swag's `--dir` parameter.
-98
View File
@@ -1,98 +0,0 @@
# Installing memos as a service on Windows
While memos first-class support is for Docker, you may also install memos as a Windows service. It will run under SYSTEM account and start automatically at system boot.
❗ All service management methods requires admin privileges. Use [gsudo](https://gerardog.github.io/gsudo/docs/install), or open a new PowerShell terminal as admin:
```powershell
Start-Process powershell -Verb RunAs
```
## Choose one of the following methods
### 1. Using [NSSM](https://nssm.cc/download)
NSSM is a lightweight service wrapper.
You may put `nssm.exe` in the same directory as `memos.exe`, or add its directory to your system PATH. Prefer the latest 64-bit version of `nssm.exe`.
```powershell
# Install memos as a service
nssm install memos "C:\path\to\memos.exe" --mode prod --port 5230
# Delay auto start
nssm set memos DisplayName "memos service"
# Configure extra service parameters
nssm set memos Description "A lightweight, self-hosted memo hub. https://usememos.com/"
# Delay auto start
nssm set memos Start SERVICE_DELAYED_AUTO_START
# Edit service using NSSM GUI
nssm edit memos
# Start the service
nssm start memos
# Remove the service, if ever needed
nssm remove memos confirm
```
### 2. Using [WinSW](https://github.com/winsw/winsw)
Find the latest release tag and download the asset `WinSW-net46x.exe`. Then, put it in the same directory as `memos.exe` and rename it to `memos-service.exe`.
Now, in the same directory, create the service configuration file `memos-service.xml`:
```xml
<service>
<id>memos</id>
<name>memos service</name>
<description>A lightweight, self-hosted memo hub. https://usememos.com/</description>
<onfailure action="restart" delay="10 sec"/>
<executable>%BASE%\memos.exe</executable>
<arguments>--mode prod --port 5230</arguments>
<delayedAutoStart>true</delayedAutoStart>
<log mode="none" />
</service>
```
Then, install the service:
```powershell
# Install the service
.\memos-service.exe install
# Start the service
.\memos-service.exe start
# Remove the service, if ever needed
.\memos-service.exe uninstall
```
### Manage the service
You may use the `net` command to manage the service:
```powershell
net start memos
net stop memos
```
Also, by using one of the provided methods, the service will appear in the Windows Services Manager `services.msc`.
## Notes
- On Windows, memos store its data in the following directory:
```powershell
$env:ProgramData\memos
# Typically, this will resolve to C:\ProgramData\memos
```
You may specify a custom directory by appending `--data <path>` to the service command line.
- If the service fails to start, you should inspect the Windows Event Viewer `eventvwr.msc`.
- Memos will be accessible at [http://localhost:5230](http://localhost:5230) by default.
+60 -80
View File
@@ -1,114 +1,95 @@
module github.com/usememos/memos
go 1.21
go 1.22
toolchain go1.22.2
require (
github.com/Masterminds/squirrel v1.5.4
github.com/aws/aws-sdk-go-v2 v1.24.0
github.com/aws/aws-sdk-go-v2/config v1.26.1
github.com/aws/aws-sdk-go-v2/credentials v1.16.12
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5
github.com/disintegration/imaging v1.6.2
github.com/go-sql-driver/mysql v1.7.1
github.com/google/cel-go v0.18.2
github.com/google/uuid v1.4.0
github.com/gorilla/feeds v1.1.2
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1
github.com/aws/aws-sdk-go-v2 v1.30.3
github.com/aws/aws-sdk-go-v2/config v1.27.26
github.com/aws/aws-sdk-go-v2/credentials v1.17.26
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.7
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2
github.com/go-sql-driver/mysql v1.8.1
github.com/google/cel-go v0.20.1
github.com/google/uuid v1.6.0
github.com/gorilla/feeds v1.2.0
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0
github.com/improbable-eng/grpc-web v0.15.0
github.com/labstack/echo/v4 v4.11.3
github.com/joho/godotenv v1.5.1
github.com/labstack/echo/v4 v4.12.0
github.com/lib/pq v1.10.9
github.com/microcosm-cc/bluemonday v1.0.26
github.com/lithammer/shortuuid/v4 v4.0.0
github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.18.1
github.com/stretchr/testify v1.8.4
github.com/swaggo/echo-swagger v1.4.1
github.com/swaggo/swag v1.16.2
go.uber.org/zap v1.26.0
golang.org/x/crypto v0.16.0
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb
golang.org/x/mod v0.14.0
golang.org/x/net v0.19.0
golang.org/x/oauth2 v0.15.0
google.golang.org/genproto/googleapis/api v0.0.0-20231127180814-3a041ad873d4
google.golang.org/grpc v1.59.0
modernc.org/sqlite v1.27.0
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
github.com/usememos/gomark v0.0.0-20240714122951-35ed01b21822
golang.org/x/crypto v0.25.0
golang.org/x/mod v0.19.0
golang.org/x/net v0.27.0
golang.org/x/oauth2 v0.21.0
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8
google.golang.org/grpc v1.64.0
modernc.org/sqlite v1.30.2
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-openapi/jsonpointer v0.20.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/spec v0.20.11 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/rs/cors v1.10.1 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/swaggo/files/v2 v2.0.0 // indirect
golang.org/x/image v0.14.0 // indirect
golang.org/x/tools v0.16.0 // indirect
google.golang.org/genproto v0.0.0-20231127180814-3a041ad873d4 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
lukechampine.com/uint128 v1.3.0 // indirect
modernc.org/cc/v3 v3.41.0 // indirect
modernc.org/ccgo/v3 v3.16.15 // indirect
modernc.org/libc v1.37.0 // indirect
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect
modernc.org/libc v1.52.1 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
nhooyr.io/websocket v1.8.10 // indirect
nhooyr.io/websocket v1.8.11 // indirect
)
require (
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 // indirect
github.com/aws/smithy-go v1.19.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.22.3 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
github.com/aws/smithy-go v1.20.3 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/labstack/gommon v0.4.1 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/posthog/posthog-go v0.0.0-20230801140217-d607812dee69
github.com/soheilhy/cmux v0.1.5
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
@@ -116,11 +97,10 @@ require (
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.31.0
google.golang.org/protobuf v1.34.2
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+155 -211
View File
@@ -1,13 +1,11 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
@@ -28,46 +26,45 @@ github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6l
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/aws/aws-sdk-go-v2 v1.24.0 h1:890+mqQ+hTpNuw0gGP6/4akolQkSToDJgHfQE7AwGuk=
github.com/aws/aws-sdk-go-v2 v1.24.0/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo=
github.com/aws/aws-sdk-go-v2/config v1.26.1 h1:z6DqMxclFGL3Zfo+4Q0rLnAZ6yVkzCRxhRMsiRQnD1o=
github.com/aws/aws-sdk-go-v2/config v1.26.1/go.mod h1:ZB+CuKHRbb5v5F0oJtGdhFTelmrxd4iWO1lf0rQwSAg=
github.com/aws/aws-sdk-go-v2/credentials v1.16.12 h1:v/WgB8NxprNvr5inKIiVVrXPuuTegM+K8nncFkr1usU=
github.com/aws/aws-sdk-go-v2/credentials v1.16.12/go.mod h1:X21k0FjEJe+/pauud82HYiQbEr9jRKY3kXEIQ4hXeTQ=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 h1:w98BT5w+ao1/r5sUuiH6JkVzjowOKeOJRHERyy1vh58=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10/go.mod h1:K2WGI7vUvkIv1HoNbfBA1bvIZ+9kL3YVmWxeKuLQsiw=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7 h1:FnLf60PtjXp8ZOzQfhJVsqF0OtYKQZWQfqOLshh8YXg=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7/go.mod h1:tDVvl8hyU6E9B8TrnNrZQEVkQlB8hjJwcgpPhgtlnNg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 h1:v+HbZaCGmOwnTTVS86Fleq0vPzOd7tnJGbFhP0stNLs=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9/go.mod h1:Xjqy+Nyj7VDLBtCMkQYOw1QYfAEZCVLrfI0ezve8wd4=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 h1:N94sVhRACtXyVcjXxrwK1SKFIJrA9pOJ5yu2eSHnmls=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9/go.mod h1:hqamLz7g1/4EJP+GH5NBhcUMLjW+gKLQabgyz6/7WAU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 h1:ugD6qzjYtB7zM5PN/ZIeaAIyefPaD82G8+SJopgvUpw=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9/go.mod h1:YD0aYBWCrPENpHolhKw2XDlTIWae2GKXT1T4o6N6hiM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 h1:/90OR2XbSYfXucBMJ4U14wrjlfleq/0SB6dZDPncgmo=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9/go.mod h1:dN/Of9/fNZet7UrQQ6kTDo/VSwKPIq94vjlU16bRARc=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 h1:Nf2sHxjMJR8CSImIVCONRi4g0Su3J+TSTbS7G0pUeMU=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9/go.mod h1:idky4TER38YIjr2cADF1/ugFMKvZV7p//pVeV5LZbF0=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 h1:iEAeF6YC3l4FzlJPP9H3Ko1TXpdjdqWffxXjp8SY6uk=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9/go.mod h1:kjsXoK23q9Z/tLBrckZLLyvjhZoS+AGrzqzUfEClvMM=
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5 h1:Keso8lIOS+IzI2MkPZyK6G0LYcK3My2LQ+T5bxghEAY=
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5/go.mod h1:vADO6Jn+Rq4nDtfwNjhgR84qkZwiC6FqCaXdw/kYwjA=
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 h1:ldSFWz9tEHAwHNmjx2Cvy1MjP5/L9kNoR0skc6wyOOM=
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5/go.mod h1:CaFfXLYL376jgbP7VKC96uFcU8Rlavak0UlAwk1Dlhc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 h1:2k9KmFawS63euAkY4/ixVNsYYwrwnd5fIvgEKkfZFNM=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5/go.mod h1:W+nd4wWDVkSUIox9bacmkBP5NMFQeTJ/xqNabpzSR38=
github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 h1:5UYvv8JUvllZsRnfrcMQ+hJ9jNICmcgKPAO1CER25Wg=
github.com/aws/aws-sdk-go-v2/service/sts v1.26.5/go.mod h1:XX5gh4CB7wAs4KhcF46G6C8a2i7eupU19dcAAE+EydU=
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY=
github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM=
github.com/aws/aws-sdk-go-v2/config v1.27.26 h1:T1kAefbKuNum/AbShMsZEro6eRkeOT8YILfE9wyjAYQ=
github.com/aws/aws-sdk-go-v2/config v1.27.26/go.mod h1:ivWHkAWFrw/nxty5Fku7soTIVdqZaZ7dw+tc5iGW3GA=
github.com/aws/aws-sdk-go-v2/credentials v1.17.26 h1:tsm8g/nJxi8+/7XyJJcP2dLrnK/5rkFp6+i2nhmz5fk=
github.com/aws/aws-sdk-go-v2/credentials v1.17.26/go.mod h1:3vAM49zkIa3q8WT6o9Ve5Z0vdByDMwmdScO0zvThTgI=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.7 h1:kNemAUX+bJFBSfPkGVZ8HFOKIadjLoI2Ua1ZKivhGSo=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.7/go.mod h1:71S2C1g/Zjn+ANmyoOqJ586OrPF9uC9iiHt9ZAT+MOw=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 h1:Z5r7SycxmSllHYmaAZPpmN8GviDrSGhMS6bldqtXZPw=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15/go.mod h1:CetW7bDE00QoGEmPUoZuRog07SGVAUVW6LFpNP0YfIg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 h1:YPYe6ZmvUfDDDELqEKtAd6bo8zxhkm+XEFEzQisqUIE=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17/go.mod h1:oBtcnYua/CgzCWYN7NZ5j7PotFDaFSUjCYVTtfyn7vw=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 h1:246A4lSTXWJw/rmlQI+TT2OcqeDMKBdyjEQrafMaQdA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15/go.mod h1:haVfg3761/WF7YPuJOER2MP0k4UAXyHaLclKXB6usDg=
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 h1:sZXIzO38GZOU+O0C+INqbH7C2yALwfMWpd64tONS/NE=
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.3 h1:Fv1vD2L65Jnp5QRsdiM64JvUM4Xe+E0JyVsRQKv6IeA=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.3/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw=
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE=
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ=
github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
@@ -75,8 +72,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
@@ -88,9 +85,8 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -98,8 +94,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I=
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
@@ -120,7 +114,6 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
@@ -128,31 +121,17 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ=
github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/spec v0.20.11 h1:J/TzFDLTt4Rcl/l1PmyErvkqlJDncGvPTMnCI39I4gY=
github.com/go-openapi/spec v0.20.11/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
@@ -161,10 +140,11 @@ github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFG
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -182,47 +162,44 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/cel-go v0.18.2 h1:L0B6sNBSVmt0OyECi8v6VOS74KOc9W/tLiWKfZABvf4=
github.com/google/cel-go v0.18.2/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84=
github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/gorilla/feeds v1.1.2 h1:pxzZ5PD3RJdhFH2FsJJ4x6PqMqbgFk1+Vez4XWBW8Iw=
github.com/gorilla/feeds v1.1.2/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc=
github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 h1:6UKoz5ujsI55KNpsJH3UwCq3T8kKbZwNZBNPuTTje8U=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -239,6 +216,8 @@ github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
@@ -254,13 +233,9 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -270,9 +245,8 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
@@ -280,34 +254,26 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.11.3 h1:Upyu3olaqSHkCjs1EJJwQ3WId8b8b1hxbogyommKktM=
github.com/labstack/echo/v4 v4.11.3/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws=
github.com/labstack/gommon v0.4.1 h1:gqEff0p/hTENGMABzezPoPSRtIh1Cvw0ueMOe0/dfOk=
github.com/labstack/gommon v0.4.1/go.mod h1:TyTrpPqxR5KMk8LKVtLmfMjeQ5FEkBYdxLYPw/WfrOM=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
@@ -318,11 +284,7 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -348,7 +310,8 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
@@ -367,8 +330,8 @@ github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnh
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
@@ -381,8 +344,6 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posthog/posthog-go v0.0.0-20230801140217-d607812dee69 h1:01dHVodha5BzrMtVmcpPeA4VYbZEsTXQ6m4123zQXJk=
github.com/posthog/posthog-go v0.0.0-20230801140217-d607812dee69/go.mod h1:migYMxlAqcnQy+3eN8mcL0b2tpKy6R+8Zc0lxwk4dKM=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
@@ -410,8 +371,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo=
github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
@@ -432,6 +393,8 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
@@ -440,13 +403,13 @@ github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNo
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.18.1 h1:rmuU42rScKWlhhJDyXZRKJQHXFX02chSVW1IvkPGiVM=
github.com/spf13/viper v1.18.1/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
@@ -456,36 +419,33 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk=
github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc=
github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw=
github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04=
github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/usememos/gomark v0.0.0-20240714122951-35ed01b21822 h1:4DQs0DJGaXLq+1eP6QfMkvcunyTVJ5OR7LK1cOm2imE=
github.com/usememos/gomark v0.0.0-20240714122951-35ed01b21822/go.mod h1:7CZRoYFQyyljzplOTeyODFR26O+wr0BbnpTWVLGfKJA=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
@@ -494,17 +454,17 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -512,19 +472,15 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8=
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc=
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -534,9 +490,10 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -553,26 +510,27 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -597,26 +555,22 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
@@ -633,12 +587,14 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -647,8 +603,6 @@ google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMt
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -657,12 +611,10 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20231127180814-3a041ad873d4 h1:W12Pwm4urIbRdGhMEg2NM9O3TWKjNcxQhs46V0ypf/k=
google.golang.org/genproto v0.0.0-20231127180814-3a041ad873d4/go.mod h1:5RBcpGRxr25RbDzY5w+dmaqpSEvl8Gwl1x2CICf60ic=
google.golang.org/genproto/googleapis/api v0.0.0-20231127180814-3a041ad873d4 h1:ZcOkrmX74HbKFYnpPY8Qsw93fC29TbJXspYKaBkSXDQ=
google.golang.org/genproto/googleapis/api v0.0.0-20231127180814-3a041ad873d4/go.mod h1:k2dtGpRrbsSyKcNPKKI5sstZkrNCZwpU/ns96JoHbGg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 h1:DC7wcm+i+P1rN3Ff07vL+OndGg5OhNddHyTA+ocPqYE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM=
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 h1:W5Xj/70xIA4x60O/IFyXivR5MGqblAb8R3w26pnD6No=
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 h1:mxSlqyb8ZAHsYDCfiXN1EDdNTdvjUJSLY+OnAUtYNYA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
@@ -676,8 +628,8 @@ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -688,15 +640,12 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
@@ -715,10 +664,7 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -726,36 +672,34 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q=
modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y=
modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0=
modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.37.0 h1:WerjebcsP6A7Jy+f2lCnHAkiSTLf7IaSftBYUtoswak=
modernc.org/libc v1.37.0/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk=
modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.17.10 h1:6wrtRozgrhCxieCeJh85QsxkX/2FFrT9hdaWPlbn4Zo=
modernc.org/ccgo/v4 v4.17.10/go.mod h1:0NBHgsqTTpm9cA5z2ccErvGZmtntSM9qD2kFAs6pjXM=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8=
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.52.1 h1:uau0VoiT5hnR+SpoWekCKbLqm7v6dhRL3hI+NQhgN3M=
modernc.org/libc v1.52.1/go.mod h1:HR4nVzFDSDizP620zcMCgjb1/8xk2lg5p/8yjfGv1IQ=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.27.0 h1:MpKAHoyYB7xqcwnUwkuD+npwEa0fojF0B5QRbN+auJ8=
modernc.org/sqlite v1.27.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.30.2 h1:IPVVkhLu5mMVnS1dQgh3h0SAACRWcVk7aoLP9Us3UCk=
modernc.org/sqlite v1.30.2/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q=
nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0=
nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
+1 -1
View File
@@ -150,7 +150,7 @@ func parseCronSegment(segment string, min int, max int) (map[int]struct{}, error
switch len(rangeParts) {
case 1:
if step != 1 {
return nil, errors.New("invalid segement step - step > 1 could be used only with the wildcard or range format")
return nil, errors.New("invalid segment step - step > 1 could be used only with the wildcard or range format")
}
parsed, err := strconv.Atoi(rangeParts[0])
if err != nil {
-66
View File
@@ -1,66 +0,0 @@
package log
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var (
// `gl` is the global logger.
// Other packages should use public methods such as Info/Error to do the logging.
// For other types of logging, e.g. logging to a separate file, they should use their own loggers.
gl *zap.Logger
gLevel zap.AtomicLevel
)
// Initializes the global console logger.
func init() {
gLevel = zap.NewAtomicLevelAt(zap.InfoLevel)
gl, _ = zap.Config{
Level: gLevel,
Development: true,
// Use "console" to print readable stacktrace.
Encoding: "console",
EncoderConfig: zap.NewDevelopmentEncoderConfig(),
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
}.Build(
// Skip one caller stack to locate the correct caller.
zap.AddCallerSkip(1),
)
}
// SetLevel wraps the zap Level's SetLevel method.
func SetLevel(level zapcore.Level) {
gLevel.SetLevel(level)
}
// EnabledLevel wraps the zap Level's Enabled method.
func EnabledLevel(level zapcore.Level) bool {
return gLevel.Enabled(level)
}
// Debug wraps the zap Logger's Debug method.
func Debug(msg string, fields ...zap.Field) {
gl.Debug(msg, fields...)
}
// Info wraps the zap Logger's Info method.
func Info(msg string, fields ...zap.Field) {
gl.Info(msg, fields...)
}
// Warn wraps the zap Logger's Warn method.
func Warn(msg string, fields ...zap.Field) {
gl.Warn(msg, fields...)
}
// Error wraps the zap Logger's Error method.
func Error(msg string, fields ...zap.Field) {
gl.Error(msg, fields...)
}
// Sync wraps the zap Logger's Sync method.
func Sync() {
_ = gl.Sync()
}
+7
View File
@@ -0,0 +1,7 @@
package util
import "regexp"
var (
UIDMatcher = regexp.MustCompile("^[a-zA-Z0-9]([a-zA-Z0-9-]{1,30}[a-zA-Z0-9])$")
)
+10 -7
View File
@@ -41,13 +41,6 @@ func GenUUID() string {
return uuid.New().String()
}
func Min(x, y int) int {
if x < y {
return x
}
return y
}
var letters = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
// RandomString returns a random string with length n.
@@ -68,3 +61,13 @@ func RandomString(n int) (string, error) {
}
return sb.String(), nil
}
// ReplaceString replaces all occurrences of old in slice with new.
func ReplaceString(slice []string, old, new string) []string {
for i, s := range slice {
if s == old {
slice[i] = new
}
}
return slice
}
-3
View File
@@ -1,3 +0,0 @@
# gomark
A markdown parser for memos. WIP
-1
View File
@@ -1 +0,0 @@
package ast
-66
View File
@@ -1,66 +0,0 @@
package ast
type BaseBlock struct {
BaseNode
}
type LineBreak struct {
BaseBlock
}
func (*LineBreak) Type() NodeType {
return LineBreakNode
}
type Paragraph struct {
BaseBlock
Children []Node
}
func (*Paragraph) Type() NodeType {
return ParagraphNode
}
type CodeBlock struct {
BaseBlock
Language string
Content string
}
func (*CodeBlock) Type() NodeType {
return CodeBlockNode
}
type Heading struct {
BaseBlock
Level int
Children []Node
}
func (*Heading) Type() NodeType {
return HeadingNode
}
type HorizontalRule struct {
BaseBlock
// Symbol is "*" or "-" or "_".
Symbol string
}
func (*HorizontalRule) Type() NodeType {
return HorizontalRuleNode
}
type Blockquote struct {
BaseBlock
Children []Node
}
func (*Blockquote) Type() NodeType {
return BlockquoteNode
}
-103
View File
@@ -1,103 +0,0 @@
package ast
type BaseInline struct {
BaseNode
}
type Text struct {
BaseInline
Content string
}
func (*Text) Type() NodeType {
return TextNode
}
type Bold struct {
BaseInline
// Symbol is "*" or "_".
Symbol string
Content string
}
func (*Bold) Type() NodeType {
return BoldNode
}
type Italic struct {
BaseInline
// Symbol is "*" or "_".
Symbol string
Content string
}
func (*Italic) Type() NodeType {
return ItalicNode
}
type BoldItalic struct {
BaseInline
// Symbol is "*" or "_".
Symbol string
Content string
}
func (*BoldItalic) Type() NodeType {
return BoldItalicNode
}
type Code struct {
BaseInline
Content string
}
func (*Code) Type() NodeType {
return CodeNode
}
type Image struct {
BaseInline
AltText string
URL string
}
func (*Image) Type() NodeType {
return ImageNode
}
type Link struct {
BaseInline
Text string
URL string
}
func (*Link) Type() NodeType {
return LinkNode
}
type Tag struct {
BaseInline
Content string
}
func (*Tag) Type() NodeType {
return TagNode
}
type Strikethrough struct {
BaseInline
Content string
}
func (*Strikethrough) Type() NodeType {
return StrikethroughNode
}
-63
View File
@@ -1,63 +0,0 @@
package ast
type NodeType uint32
const (
UnknownNode NodeType = iota
// Block nodes.
LineBreakNode
ParagraphNode
CodeBlockNode
HeadingNode
HorizontalRuleNode
BlockquoteNode
// Inline nodes.
TextNode
BoldNode
ItalicNode
BoldItalicNode
CodeNode
ImageNode
LinkNode
TagNode
StrikethroughNode
)
type Node interface {
// Type returns a node type.
Type() NodeType
// PrevSibling returns a previous sibling node of this node.
PrevSibling() Node
// NextSibling returns a next sibling node of this node.
NextSibling() Node
// SetPrevSibling sets a previous sibling node to this node.
SetPrevSibling(Node)
// SetNextSibling sets a next sibling node to this node.
SetNextSibling(Node)
}
type BaseNode struct {
prevSibling Node
nextSibling Node
}
func (n *BaseNode) PrevSibling() Node {
return n.prevSibling
}
func (n *BaseNode) NextSibling() Node {
return n.nextSibling
}
func (n *BaseNode) SetPrevSibling(node Node) {
n.prevSibling = node
}
func (n *BaseNode) SetNextSibling(node Node) {
n.nextSibling = node
}
-1
View File
@@ -1 +0,0 @@
package gomark
-52
View File
@@ -1,52 +0,0 @@
package parser
import (
"errors"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
type BlockquoteParser struct{}
func NewBlockquoteParser() *BlockquoteParser {
return &BlockquoteParser{}
}
func (*BlockquoteParser) Match(tokens []*tokenizer.Token) (int, bool) {
if len(tokens) < 4 {
return 0, false
}
if tokens[0].Type != tokenizer.GreaterThan || tokens[1].Type != tokenizer.Space {
return 0, false
}
contentTokens := []*tokenizer.Token{}
for _, token := range tokens[2:] {
contentTokens = append(contentTokens, token)
if token.Type == tokenizer.Newline {
break
}
}
if len(contentTokens) == 0 {
return 0, false
}
return len(contentTokens) + 2, true
}
func (p *BlockquoteParser) Parse(tokens []*tokenizer.Token) (ast.Node, error) {
size, ok := p.Match(tokens)
if size == 0 || !ok {
return nil, errors.New("not matched")
}
contentTokens := tokens[2:size]
children, err := ParseInline(contentTokens)
if err != nil {
return nil, err
}
return &ast.Blockquote{
Children: children,
}, nil
}
-49
View File
@@ -1,49 +0,0 @@
package parser
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
func TestBlockquoteParser(t *testing.T) {
tests := []struct {
text string
blockquote ast.Node
}{
{
text: "> Hello world",
blockquote: &ast.Blockquote{
Children: []ast.Node{
&ast.Text{
Content: "Hello world",
},
},
},
},
{
text: "> Hello\nworld",
blockquote: &ast.Blockquote{
Children: []ast.Node{
&ast.Text{
Content: "Hello",
},
&ast.LineBreak{},
},
},
},
{
text: ">Hello\nworld",
blockquote: nil,
},
}
for _, test := range tests {
tokens := tokenizer.Tokenize(test.text)
node, _ := NewBlockquoteParser().Parse(tokens)
require.Equal(t, StringifyNodes([]ast.Node{test.blockquote}), StringifyNodes([]ast.Node{node}))
}
}
-60
View File
@@ -1,60 +0,0 @@
package parser
import (
"errors"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
type BoldParser struct{}
func NewBoldParser() InlineParser {
return &BoldParser{}
}
func (*BoldParser) Match(tokens []*tokenizer.Token) (int, bool) {
if len(tokens) < 5 {
return 0, false
}
prefixTokens := tokens[:2]
if prefixTokens[0].Type != prefixTokens[1].Type {
return 0, false
}
prefixTokenType := prefixTokens[0].Type
if prefixTokenType != tokenizer.Asterisk && prefixTokenType != tokenizer.Underline {
return 0, false
}
cursor, matched := 2, false
for ; cursor < len(tokens)-1; cursor++ {
token, nextToken := tokens[cursor], tokens[cursor+1]
if token.Type == tokenizer.Newline || nextToken.Type == tokenizer.Newline {
return 0, false
}
if token.Type == prefixTokenType && nextToken.Type == prefixTokenType {
matched = true
break
}
}
if !matched {
return 0, false
}
return cursor + 2, true
}
func (p *BoldParser) Parse(tokens []*tokenizer.Token) (ast.Node, error) {
size, ok := p.Match(tokens)
if size == 0 || !ok {
return nil, errors.New("not matched")
}
prefixTokenType := tokens[0].Type
contentTokens := tokens[2 : size-2]
return &ast.Bold{
Symbol: prefixTokenType,
Content: tokenizer.Stringify(contentTokens),
}, nil
}
-60
View File
@@ -1,60 +0,0 @@
package parser
import (
"errors"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
type BoldItalicParser struct{}
func NewBoldItalicParser() InlineParser {
return &BoldItalicParser{}
}
func (*BoldItalicParser) Match(tokens []*tokenizer.Token) (int, bool) {
if len(tokens) < 7 {
return 0, false
}
prefixTokens := tokens[:3]
if prefixTokens[0].Type != prefixTokens[1].Type || prefixTokens[0].Type != prefixTokens[2].Type || prefixTokens[1].Type != prefixTokens[2].Type {
return 0, false
}
prefixTokenType := prefixTokens[0].Type
if prefixTokenType != tokenizer.Asterisk && prefixTokenType != tokenizer.Underline {
return 0, false
}
cursor, matched := 3, false
for ; cursor < len(tokens)-2; cursor++ {
token, nextToken, endToken := tokens[cursor], tokens[cursor+1], tokens[cursor+2]
if token.Type == tokenizer.Newline || nextToken.Type == tokenizer.Newline || endToken.Type == tokenizer.Newline {
return 0, false
}
if token.Type == prefixTokenType && nextToken.Type == prefixTokenType && endToken.Type == prefixTokenType {
matched = true
break
}
}
if !matched {
return 0, false
}
return cursor + 3, true
}
func (p *BoldItalicParser) Parse(tokens []*tokenizer.Token) (ast.Node, error) {
size, ok := p.Match(tokens)
if size == 0 || !ok {
return nil, errors.New("not matched")
}
prefixTokenType := tokens[0].Type
contentTokens := tokens[3 : size-3]
return &ast.BoldItalic{
Symbol: prefixTokenType,
Content: tokenizer.Stringify(contentTokens),
}, nil
}
-50
View File
@@ -1,50 +0,0 @@
package parser
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
func TestBoldItalicParser(t *testing.T) {
tests := []struct {
text string
boldItalic ast.Node
}{
{
text: "*Hello world!",
boldItalic: nil,
},
{
text: "***Hello***",
boldItalic: &ast.BoldItalic{
Symbol: "*",
Content: "Hello",
},
},
{
text: "*** Hello ***",
boldItalic: &ast.BoldItalic{
Symbol: "*",
Content: " Hello ",
},
},
{
text: "*** Hello * *",
boldItalic: nil,
},
{
text: "*** Hello **",
boldItalic: nil,
},
}
for _, test := range tests {
tokens := tokenizer.Tokenize(test.text)
node, _ := NewBoldItalicParser().Parse(tokens)
require.Equal(t, StringifyNodes([]ast.Node{test.boldItalic}), StringifyNodes([]ast.Node{node}))
}
}
-50
View File
@@ -1,50 +0,0 @@
package parser
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
func TestBoldParser(t *testing.T) {
tests := []struct {
text string
bold ast.Node
}{
{
text: "*Hello world!",
bold: nil,
},
{
text: "**Hello**",
bold: &ast.Bold{
Symbol: "*",
Content: "Hello",
},
},
{
text: "** Hello **",
bold: &ast.Bold{
Symbol: "*",
Content: " Hello ",
},
},
{
text: "** Hello * *",
bold: nil,
},
{
text: "* * Hello **",
bold: nil,
},
}
for _, test := range tests {
tokens := tokenizer.Tokenize(test.text)
node, _ := NewBoldParser().Parse(tokens)
require.Equal(t, StringifyNodes([]ast.Node{test.bold}), StringifyNodes([]ast.Node{node}))
}
}
-51
View File
@@ -1,51 +0,0 @@
package parser
import (
"errors"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
type CodeParser struct{}
func NewCodeParser() *CodeParser {
return &CodeParser{}
}
func (*CodeParser) Match(tokens []*tokenizer.Token) (int, bool) {
if len(tokens) < 3 {
return 0, false
}
if tokens[0].Type != tokenizer.Backtick {
return 0, false
}
contentTokens, matched := []*tokenizer.Token{}, false
for _, token := range tokens[1:] {
if token.Type == tokenizer.Newline {
return 0, false
}
if token.Type == tokenizer.Backtick {
matched = true
break
}
contentTokens = append(contentTokens, token)
}
if !matched || len(contentTokens) == 0 {
return 0, false
}
return len(contentTokens) + 2, true
}
func (p *CodeParser) Parse(tokens []*tokenizer.Token) (ast.Node, error) {
size, ok := p.Match(tokens)
if size == 0 || !ok {
return nil, errors.New("not matched")
}
contentTokens := tokens[1 : size-1]
return &ast.Code{
Content: tokenizer.Stringify(contentTokens),
}, nil
}
-79
View File
@@ -1,79 +0,0 @@
package parser
import (
"errors"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
type CodeBlockParser struct {
Language string
Content string
}
func NewCodeBlockParser() *CodeBlockParser {
return &CodeBlockParser{}
}
func (*CodeBlockParser) Match(tokens []*tokenizer.Token) (int, bool) {
if len(tokens) < 9 {
return 0, false
}
if tokens[0].Type != tokenizer.Backtick || tokens[1].Type != tokenizer.Backtick || tokens[2].Type != tokenizer.Backtick {
return 0, false
}
if tokens[3].Type != tokenizer.Newline && tokens[4].Type != tokenizer.Newline {
return 0, false
}
cursor := 4
if tokens[3].Type != tokenizer.Newline {
cursor = 5
}
matched := false
for ; cursor < len(tokens)-3; cursor++ {
if tokens[cursor].Type == tokenizer.Newline && tokens[cursor+1].Type == tokenizer.Backtick && tokens[cursor+2].Type == tokenizer.Backtick && tokens[cursor+3].Type == tokenizer.Backtick {
if cursor+3 == len(tokens)-1 {
cursor += 4
matched = true
break
} else if tokens[cursor+4].Type == tokenizer.Newline {
cursor += 5
matched = true
break
}
}
}
if !matched {
return 0, false
}
return cursor, true
}
func (p *CodeBlockParser) Parse(tokens []*tokenizer.Token) (ast.Node, error) {
size, ok := p.Match(tokens)
if size == 0 || !ok {
return nil, errors.New("not matched")
}
languageToken := tokens[3]
contentStart, contentEnd := 5, size-4
if languageToken.Type == tokenizer.Newline {
languageToken = nil
contentStart = 4
}
if tokens[size-1].Type == tokenizer.Newline {
contentEnd = size - 5
}
codeBlock := &ast.CodeBlock{
Content: tokenizer.Stringify(tokens[contentStart:contentEnd]),
}
if languageToken != nil {
codeBlock.Language = languageToken.String()
}
return codeBlock, nil
}
-64
View File
@@ -1,64 +0,0 @@
package parser
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
func TestCodeBlockParser(t *testing.T) {
tests := []struct {
text string
codeBlock ast.Node
}{
{
text: "```Hello world!```",
codeBlock: nil,
},
{
text: "```\nHello\n```",
codeBlock: &ast.CodeBlock{
Language: "",
Content: "Hello",
},
},
{
text: "```\nHello world!\n```",
codeBlock: &ast.CodeBlock{
Language: "",
Content: "Hello world!",
},
},
{
text: "```java\nHello \n world!\n```",
codeBlock: &ast.CodeBlock{
Language: "java",
Content: "Hello \n world!",
},
},
{
text: "```java\nHello \n world!\n```111",
codeBlock: nil,
},
{
text: "```java\nHello \n world!\n``` 111",
codeBlock: nil,
},
{
text: "```java\nHello \n world!\n```\n123123",
codeBlock: &ast.CodeBlock{
Language: "java",
Content: "Hello \n world!",
},
},
}
for _, test := range tests {
tokens := tokenizer.Tokenize(test.text)
node, _ := NewCodeBlockParser().Parse(tokens)
require.Equal(t, StringifyNodes([]ast.Node{test.codeBlock}), StringifyNodes([]ast.Node{node}))
}
}
-38
View File
@@ -1,38 +0,0 @@
package parser
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
func TestCodeParser(t *testing.T) {
tests := []struct {
text string
code ast.Node
}{
{
text: "`Hello world!",
code: nil,
},
{
text: "`Hello world!`",
code: &ast.Code{
Content: "Hello world!",
},
},
{
text: "`Hello \nworld!`",
code: nil,
},
}
for _, test := range tests {
tokens := tokenizer.Tokenize(test.text)
node, _ := NewCodeParser().Parse(tokens)
require.Equal(t, StringifyNodes([]ast.Node{test.code}), StringifyNodes([]ast.Node{node}))
}
}
-76
View File
@@ -1,76 +0,0 @@
package parser
import (
"errors"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
type HeadingParser struct{}
func NewHeadingParser() *HeadingParser {
return &HeadingParser{}
}
func (*HeadingParser) Match(tokens []*tokenizer.Token) (int, bool) {
cursor := 0
for _, token := range tokens {
if token.Type == tokenizer.Hash {
cursor++
} else {
break
}
}
if len(tokens) <= cursor+1 {
return 0, false
}
if tokens[cursor].Type != tokenizer.Space {
return 0, false
}
level := cursor
if level == 0 || level > 6 {
return 0, false
}
cursor++
contentTokens := []*tokenizer.Token{}
for _, token := range tokens[cursor:] {
contentTokens = append(contentTokens, token)
cursor++
if token.Type == tokenizer.Newline {
break
}
}
if len(contentTokens) == 0 {
return 0, false
}
return cursor, true
}
func (p *HeadingParser) Parse(tokens []*tokenizer.Token) (ast.Node, error) {
size, ok := p.Match(tokens)
if size == 0 || !ok {
return nil, errors.New("not matched")
}
level := 0
for _, token := range tokens {
if token.Type == tokenizer.Hash {
level++
} else {
break
}
}
contentTokens := tokens[level+1 : size]
children, err := ParseInline(contentTokens)
if err != nil {
return nil, err
}
return &ast.Heading{
Level: level,
Children: children,
}, nil
}
-82
View File
@@ -1,82 +0,0 @@
package parser
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
func TestHeadingParser(t *testing.T) {
tests := []struct {
text string
heading ast.Node
}{
{
text: "*Hello world",
heading: nil,
},
{
text: "## Hello World",
heading: &ast.Heading{
Level: 2,
Children: []ast.Node{
&ast.Text{
Content: "Hello World",
},
},
},
},
{
text: "# # Hello World",
heading: &ast.Heading{
Level: 1,
Children: []ast.Node{
&ast.Text{
Content: "# Hello World",
},
},
},
},
{
text: " # 123123 Hello World",
heading: nil,
},
{
text: `# 123
Hello World`,
heading: &ast.Heading{
Level: 1,
Children: []ast.Node{
&ast.Text{
Content: "123 ",
},
&ast.LineBreak{},
},
},
},
{
text: "### **Hello** World",
heading: &ast.Heading{
Level: 3,
Children: []ast.Node{
&ast.Bold{
Symbol: "*",
Content: "Hello",
},
&ast.Text{
Content: " World",
},
},
},
},
}
for _, test := range tests {
tokens := tokenizer.Tokenize(test.text)
node, _ := NewHeadingParser().Parse(tokens)
require.Equal(t, StringifyNodes([]ast.Node{test.heading}), StringifyNodes([]ast.Node{node}))
}
}
-44
View File
@@ -1,44 +0,0 @@
package parser
import (
"errors"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
type HorizontalRuleParser struct{}
func NewHorizontalRuleParser() *HorizontalRuleParser {
return &HorizontalRuleParser{}
}
func (*HorizontalRuleParser) Match(tokens []*tokenizer.Token) (int, bool) {
if len(tokens) < 3 {
return 0, false
}
if tokens[0].Type != tokens[1].Type || tokens[0].Type != tokens[2].Type || tokens[1].Type != tokens[2].Type {
return 0, false
}
if tokens[0].Type != tokenizer.Dash && tokens[0].Type != tokenizer.Underline && tokens[0].Type != tokenizer.Asterisk {
return 0, false
}
if len(tokens) > 3 && tokens[3].Type != tokenizer.Newline {
return 0, false
}
if len(tokens) == 3 {
return 3, true
}
return 4, true
}
func (p *HorizontalRuleParser) Parse(tokens []*tokenizer.Token) (ast.Node, error) {
size, ok := p.Match(tokens)
if size == 0 || !ok {
return nil, errors.New("not matched")
}
return &ast.HorizontalRule{
Symbol: tokens[0].Type,
}, nil
}
@@ -1,56 +0,0 @@
package parser
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
func TestHorizontalRuleParser(t *testing.T) {
tests := []struct {
text string
horizontalRule ast.Node
}{
{
text: "---",
horizontalRule: &ast.HorizontalRule{
Symbol: "-",
},
},
{
text: "---\naaa",
horizontalRule: &ast.HorizontalRule{
Symbol: "-",
},
},
{
text: "****",
horizontalRule: nil,
},
{
text: "***",
horizontalRule: &ast.HorizontalRule{
Symbol: "*",
},
},
{
text: "-*-",
horizontalRule: nil,
},
{
text: "___",
horizontalRule: &ast.HorizontalRule{
Symbol: "_",
},
},
}
for _, test := range tests {
tokens := tokenizer.Tokenize(test.text)
node, _ := NewHorizontalRuleParser().Parse(tokens)
require.Equal(t, StringifyNodes([]ast.Node{test.horizontalRule}), StringifyNodes([]ast.Node{node}))
}
}
-75
View File
@@ -1,75 +0,0 @@
package parser
import (
"errors"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
type ImageParser struct{}
func NewImageParser() *ImageParser {
return &ImageParser{}
}
func (*ImageParser) Match(tokens []*tokenizer.Token) (int, bool) {
if len(tokens) < 5 {
return 0, false
}
if tokens[0].Type != tokenizer.ExclamationMark {
return 0, false
}
if tokens[1].Type != tokenizer.LeftSquareBracket {
return 0, false
}
cursor, altText := 2, ""
for ; cursor < len(tokens)-2; cursor++ {
if tokens[cursor].Type == tokenizer.Newline {
return 0, false
}
if tokens[cursor].Type == tokenizer.RightSquareBracket {
break
}
altText += tokens[cursor].Value
}
if tokens[cursor+1].Type != tokenizer.LeftParenthesis {
return 0, false
}
cursor += 2
contentTokens, matched := []*tokenizer.Token{}, false
for _, token := range tokens[cursor:] {
if token.Type == tokenizer.Newline || token.Type == tokenizer.Space {
return 0, false
}
if token.Type == tokenizer.RightParenthesis {
matched = true
break
}
contentTokens = append(contentTokens, token)
}
if !matched || len(contentTokens) == 0 {
return 0, false
}
return cursor + len(contentTokens) + 1, true
}
func (p *ImageParser) Parse(tokens []*tokenizer.Token) (ast.Node, error) {
size, ok := p.Match(tokens)
if size == 0 || !ok {
return nil, errors.New("not matched")
}
altTextTokens := []*tokenizer.Token{}
for _, token := range tokens[2:] {
if token.Type == tokenizer.RightSquareBracket {
break
}
altTextTokens = append(altTextTokens, token)
}
contentTokens := tokens[2+len(altTextTokens)+2 : size-1]
return &ast.Image{
AltText: tokenizer.Stringify(altTextTokens),
URL: tokenizer.Stringify(contentTokens),
}, nil
}
-45
View File
@@ -1,45 +0,0 @@
package parser
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
func TestImageParser(t *testing.T) {
tests := []struct {
text string
image ast.Node
}{
{
text: "![](https://example.com)",
image: &ast.Image{
AltText: "",
URL: "https://example.com",
},
},
{
text: "! [](https://example.com)",
image: nil,
},
{
text: "![alte]( htt ps :/ /example.com)",
image: nil,
},
{
text: "![al te](https://example.com)",
image: &ast.Image{
AltText: "al te",
URL: "https://example.com",
},
},
}
for _, test := range tests {
tokens := tokenizer.Tokenize(test.text)
node, _ := NewImageParser().Parse(tokens)
require.Equal(t, StringifyNodes([]ast.Node{test.image}), StringifyNodes([]ast.Node{node}))
}
}
-59
View File
@@ -1,59 +0,0 @@
package parser
import (
"errors"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
type ItalicParser struct {
ContentTokens []*tokenizer.Token
}
func NewItalicParser() *ItalicParser {
return &ItalicParser{}
}
func (*ItalicParser) Match(tokens []*tokenizer.Token) (int, bool) {
if len(tokens) < 3 {
return 0, false
}
prefixTokens := tokens[:1]
if prefixTokens[0].Type != tokenizer.Asterisk && prefixTokens[0].Type != tokenizer.Underline {
return 0, false
}
prefixTokenType := prefixTokens[0].Type
contentTokens := []*tokenizer.Token{}
matched := false
for _, token := range tokens[1:] {
if token.Type == tokenizer.Newline {
return 0, false
}
if token.Type == prefixTokenType {
matched = true
break
}
contentTokens = append(contentTokens, token)
}
if !matched || len(contentTokens) == 0 {
return 0, false
}
return len(contentTokens) + 2, true
}
func (p *ItalicParser) Parse(tokens []*tokenizer.Token) (ast.Node, error) {
size, ok := p.Match(tokens)
if size == 0 || !ok {
return nil, errors.New("not matched")
}
prefixTokenType := tokens[0].Type
contentTokens := tokens[1 : size-1]
return &ast.Italic{
Symbol: prefixTokenType,
Content: tokenizer.Stringify(contentTokens),
}, nil
}
-49
View File
@@ -1,49 +0,0 @@
package parser
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
func TestItalicParser(t *testing.T) {
tests := []struct {
text string
italic ast.Node
}{
{
text: "*Hello world!",
italic: nil,
},
{
text: "*Hello*",
italic: &ast.Italic{
Symbol: "*",
Content: "Hello",
},
},
{
text: "* Hello *",
italic: &ast.Italic{
Symbol: "*",
Content: " Hello ",
},
},
{
text: "*1* Hello * *",
italic: &ast.Italic{
Symbol: "*",
Content: "1",
},
},
}
for _, test := range tests {
tokens := tokenizer.Tokenize(test.text)
node, _ := NewItalicParser().Parse(tokens)
require.Equal(t, StringifyNodes([]ast.Node{test.italic}), StringifyNodes([]ast.Node{node}))
}
}
-33
View File
@@ -1,33 +0,0 @@
package parser
import (
"errors"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
type LineBreakParser struct{}
func NewLineBreakParser() *LineBreakParser {
return &LineBreakParser{}
}
func (*LineBreakParser) Match(tokens []*tokenizer.Token) (int, bool) {
if len(tokens) == 0 {
return 0, false
}
if tokens[0].Type != tokenizer.Newline {
return 0, false
}
return 1, true
}
func (p *LineBreakParser) Parse(tokens []*tokenizer.Token) (ast.Node, error) {
size, ok := p.Match(tokens)
if size == 0 || !ok {
return nil, errors.New("not matched")
}
return &ast.LineBreak{}, nil
}
-74
View File
@@ -1,74 +0,0 @@
package parser
import (
"errors"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
type LinkParser struct{}
func NewLinkParser() *LinkParser {
return &LinkParser{}
}
func (*LinkParser) Match(tokens []*tokenizer.Token) (int, bool) {
if len(tokens) < 5 {
return 0, false
}
if tokens[0].Type != tokenizer.LeftSquareBracket {
return 0, false
}
textTokens := []*tokenizer.Token{}
for _, token := range tokens[1:] {
if token.Type == tokenizer.Newline {
return 0, false
}
if token.Type == tokenizer.RightSquareBracket {
break
}
textTokens = append(textTokens, token)
}
if len(textTokens)+4 >= len(tokens) {
return 0, false
}
if tokens[2+len(textTokens)].Type != tokenizer.LeftParenthesis {
return 0, false
}
urlTokens := []*tokenizer.Token{}
for _, token := range tokens[3+len(textTokens):] {
if token.Type == tokenizer.Newline || token.Type == tokenizer.Space {
return 0, false
}
if token.Type == tokenizer.RightParenthesis {
break
}
urlTokens = append(urlTokens, token)
}
if 4+len(urlTokens)+len(textTokens) > len(tokens) {
return 0, false
}
return 4 + len(urlTokens) + len(textTokens), true
}
func (p *LinkParser) Parse(tokens []*tokenizer.Token) (ast.Node, error) {
size, ok := p.Match(tokens)
if size == 0 || !ok {
return nil, errors.New("not matched")
}
textTokens := []*tokenizer.Token{}
for _, token := range tokens[1:] {
if token.Type == tokenizer.RightSquareBracket {
break
}
textTokens = append(textTokens, token)
}
urlTokens := tokens[2+len(textTokens)+1 : size-1]
return &ast.Link{
Text: tokenizer.Stringify(textTokens),
URL: tokenizer.Stringify(urlTokens),
}, nil
}
-45
View File
@@ -1,45 +0,0 @@
package parser
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
func TestLinkParser(t *testing.T) {
tests := []struct {
text string
link ast.Node
}{
{
text: "[](https://example.com)",
link: &ast.Link{
Text: "",
URL: "https://example.com",
},
},
{
text: "! [](https://example.com)",
link: nil,
},
{
text: "[alte]( htt ps :/ /example.com)",
link: nil,
},
{
text: "[hello world](https://example.com)",
link: &ast.Link{
Text: "hello world",
URL: "https://example.com",
},
},
}
for _, test := range tests {
tokens := tokenizer.Tokenize(test.text)
node, _ := NewLinkParser().Parse(tokens)
require.Equal(t, StringifyNodes([]ast.Node{test.link}), StringifyNodes([]ast.Node{node}))
}
}
-49
View File
@@ -1,49 +0,0 @@
package parser
import (
"errors"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
type ParagraphParser struct {
ContentTokens []*tokenizer.Token
}
func NewParagraphParser() *ParagraphParser {
return &ParagraphParser{}
}
func (*ParagraphParser) Match(tokens []*tokenizer.Token) (int, bool) {
contentTokens := []*tokenizer.Token{}
for _, token := range tokens {
contentTokens = append(contentTokens, token)
if token.Type == tokenizer.Newline {
break
}
}
if len(contentTokens) == 0 {
return 0, false
}
if len(contentTokens) == 1 && contentTokens[0].Type == tokenizer.Newline {
return 0, false
}
return len(contentTokens), true
}
func (p *ParagraphParser) Parse(tokens []*tokenizer.Token) (ast.Node, error) {
size, ok := p.Match(tokens)
if size == 0 || !ok {
return nil, errors.New("not matched")
}
contentTokens := tokens[:size]
children, err := ParseInline(contentTokens)
if err != nil {
return nil, err
}
return &ast.Paragraph{
Children: children,
}, nil
}
-64
View File
@@ -1,64 +0,0 @@
package parser
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
func TestParagraphParser(t *testing.T) {
tests := []struct {
text string
paragraph ast.Node
}{
{
text: "",
paragraph: nil,
},
{
text: "\n",
paragraph: nil,
},
{
text: "Hello world!",
paragraph: &ast.Paragraph{
Children: []ast.Node{
&ast.Text{
Content: "Hello world!",
},
},
},
},
{
text: "Hello world!\n",
paragraph: &ast.Paragraph{
Children: []ast.Node{
&ast.Text{
Content: "Hello world!",
},
&ast.LineBreak{},
},
},
},
{
text: "Hello world!\n\nNew paragraph.",
paragraph: &ast.Paragraph{
Children: []ast.Node{
&ast.Text{
Content: "Hello world!",
},
&ast.LineBreak{},
},
},
},
}
for _, test := range tests {
tokens := tokenizer.Tokenize(test.text)
node, _ := NewParagraphParser().Parse(tokens)
require.Equal(t, StringifyNodes([]ast.Node{test.paragraph}), StringifyNodes([]ast.Node{node}))
}
}
-106
View File
@@ -1,106 +0,0 @@
package parser
import (
"errors"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
type Context struct {
BlockParsers []BlockParser
InlineParsers []InlineParser
}
type BaseParser interface {
Match(tokens []*tokenizer.Token) (int, bool)
Parse(tokens []*tokenizer.Token) (ast.Node, error)
}
type InlineParser interface {
BaseParser
}
type BlockParser interface {
BaseParser
}
var defaultBlockParsers = []BlockParser{
NewCodeBlockParser(),
NewHorizontalRuleParser(),
NewHeadingParser(),
NewBlockquoteParser(),
NewParagraphParser(),
NewLineBreakParser(),
}
func Parse(tokens []*tokenizer.Token) ([]ast.Node, error) {
nodes := []ast.Node{}
var prevNode ast.Node
for len(tokens) > 0 {
for _, blockParser := range defaultBlockParsers {
size, matched := blockParser.Match(tokens)
if matched {
node, err := blockParser.Parse(tokens)
if err != nil {
return nil, errors.New("parse error")
}
tokens = tokens[size:]
if prevNode != nil {
prevNode.SetNextSibling(node)
node.SetPrevSibling(prevNode)
}
prevNode = node
nodes = append(nodes, node)
break
}
}
}
return nodes, nil
}
var defaultInlineParsers = []InlineParser{
NewBoldItalicParser(),
NewImageParser(),
NewLinkParser(),
NewBoldParser(),
NewItalicParser(),
NewCodeParser(),
NewTagParser(),
NewStrikethroughParser(),
NewLineBreakParser(),
NewTextParser(),
}
func ParseInline(tokens []*tokenizer.Token) ([]ast.Node, error) {
nodes := []ast.Node{}
var prevNode ast.Node
for len(tokens) > 0 {
for _, inlineParser := range defaultInlineParsers {
size, matched := inlineParser.Match(tokens)
if matched {
node, err := inlineParser.Parse(tokens)
if err != nil {
return nil, errors.New("parse error")
}
tokens = tokens[size:]
if prevNode != nil {
// Merge text nodes if possible.
if prevNode.Type() == ast.TextNode && node.Type() == ast.TextNode {
prevNode.(*ast.Text).Content += node.(*ast.Text).Content
break
}
prevNode.SetNextSibling(node)
node.SetPrevSibling(prevNode)
}
nodes = append(nodes, node)
prevNode = node
break
}
}
}
return nodes, nil
}
-170
View File
@@ -1,170 +0,0 @@
package parser
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
func TestParser(t *testing.T) {
tests := []struct {
text string
nodes []ast.Node
}{
{
text: "Hello world!",
nodes: []ast.Node{
&ast.Paragraph{
Children: []ast.Node{
&ast.Text{
Content: "Hello world!",
},
},
},
},
},
{
text: "**Hello** world!",
nodes: []ast.Node{
&ast.Paragraph{
Children: []ast.Node{
&ast.Bold{
Symbol: "*",
Content: "Hello",
},
&ast.Text{
Content: " world!",
},
},
},
},
},
{
text: "Hello **world**!\nHere is a new line.",
nodes: []ast.Node{
&ast.Paragraph{
Children: []ast.Node{
&ast.Text{
Content: "Hello ",
},
&ast.Bold{
Symbol: "*",
Content: "world",
},
&ast.Text{
Content: "!",
},
&ast.LineBreak{},
},
},
&ast.Paragraph{
Children: []ast.Node{
&ast.Text{
Content: "Here is a new line.",
},
},
},
},
},
{
text: "Hello **world**!\n```javascript\nconsole.log(\"Hello world!\");\n```",
nodes: []ast.Node{
&ast.Paragraph{
Children: []ast.Node{
&ast.Text{
Content: "Hello ",
},
&ast.Bold{
Symbol: "*",
Content: "world",
},
&ast.Text{
Content: "!",
},
&ast.LineBreak{},
},
},
&ast.CodeBlock{
Language: "javascript",
Content: "console.log(\"Hello world!\");",
},
},
},
{
text: "Hello world!\n\nNew paragraph.",
nodes: []ast.Node{
&ast.Paragraph{
Children: []ast.Node{
&ast.Text{
Content: "Hello world!",
},
&ast.LineBreak{},
},
},
&ast.LineBreak{},
&ast.Paragraph{
Children: []ast.Node{
&ast.Text{
Content: "New paragraph.",
},
},
},
},
},
}
for _, test := range tests {
tokens := tokenizer.Tokenize(test.text)
nodes, _ := Parse(tokens)
require.Equal(t, StringifyNodes(test.nodes), StringifyNodes(nodes))
}
}
func StringifyNodes(nodes []ast.Node) string {
var result string
for _, node := range nodes {
if node != nil {
result += StringifyNode(node)
}
}
return result
}
func StringifyNode(node ast.Node) string {
switch n := node.(type) {
case *ast.LineBreak:
return "LineBreak()"
case *ast.CodeBlock:
return "CodeBlock(" + n.Language + ", " + n.Content + ")"
case *ast.Paragraph:
return "Paragraph(" + StringifyNodes(n.Children) + ")"
case *ast.Heading:
return "Heading(" + StringifyNodes(n.Children) + ")"
case *ast.HorizontalRule:
return "HorizontalRule(" + n.Symbol + ")"
case *ast.Blockquote:
return "Blockquote(" + StringifyNodes(n.Children) + ")"
case *ast.Text:
return "Text(" + n.Content + ")"
case *ast.Bold:
return "Bold(" + n.Symbol + n.Content + n.Symbol + ")"
case *ast.Italic:
return "Italic(" + n.Symbol + n.Content + n.Symbol + ")"
case *ast.BoldItalic:
return "BoldItalic(" + n.Symbol + n.Content + n.Symbol + ")"
case *ast.Code:
return "Code(" + n.Content + ")"
case *ast.Image:
return "Image(" + n.URL + ", " + n.AltText + ")"
case *ast.Link:
return "Link(" + n.Text + ", " + n.URL + ")"
case *ast.Tag:
return "Tag(" + n.Content + ")"
case *ast.Strikethrough:
return "Strikethrough(" + n.Content + ")"
}
return ""
}
-51
View File
@@ -1,51 +0,0 @@
package parser
import (
"errors"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
type StrikethroughParser struct{}
func NewStrikethroughParser() *StrikethroughParser {
return &StrikethroughParser{}
}
func (*StrikethroughParser) Match(tokens []*tokenizer.Token) (int, bool) {
if len(tokens) < 5 {
return 0, false
}
if tokens[0].Type != tokenizer.Tilde || tokens[1].Type != tokenizer.Tilde {
return 0, false
}
cursor, matched := 2, false
for ; cursor < len(tokens)-1; cursor++ {
token, nextToken := tokens[cursor], tokens[cursor+1]
if token.Type == tokenizer.Newline || nextToken.Type == tokenizer.Newline {
return 0, false
}
if token.Type == tokenizer.Tilde && nextToken.Type == tokenizer.Tilde {
matched = true
break
}
}
if !matched {
return 0, false
}
return cursor + 2, true
}
func (p *StrikethroughParser) Parse(tokens []*tokenizer.Token) (ast.Node, error) {
size, ok := p.Match(tokens)
if size == 0 || !ok {
return nil, errors.New("not matched")
}
contentTokens := tokens[2 : size-2]
return &ast.Strikethrough{
Content: tokenizer.Stringify(contentTokens),
}, nil
}
@@ -1,46 +0,0 @@
package parser
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
func TestStrikethroughParser(t *testing.T) {
tests := []struct {
text string
strikethrough ast.Node
}{
{
text: "~~Hello world",
strikethrough: nil,
},
{
text: "~~Hello~~",
strikethrough: &ast.Strikethrough{
Content: "Hello",
},
},
{
text: "~~ Hello ~~",
strikethrough: &ast.Strikethrough{
Content: " Hello ",
},
},
{
text: "~~1~~ Hello ~~~",
strikethrough: &ast.Strikethrough{
Content: "1",
},
},
}
for _, test := range tests {
tokens := tokenizer.Tokenize(test.text)
node, _ := NewStrikethroughParser().Parse(tokens)
require.Equal(t, StringifyNodes([]ast.Node{test.strikethrough}), StringifyNodes([]ast.Node{node}))
}
}
-47
View File
@@ -1,47 +0,0 @@
package parser
import (
"errors"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
type TagParser struct{}
func NewTagParser() *TagParser {
return &TagParser{}
}
func (*TagParser) Match(tokens []*tokenizer.Token) (int, bool) {
if len(tokens) < 2 {
return 0, false
}
if tokens[0].Type != tokenizer.Hash {
return 0, false
}
contentTokens := []*tokenizer.Token{}
for _, token := range tokens[1:] {
if token.Type == tokenizer.Newline || token.Type == tokenizer.Space || token.Type == tokenizer.Hash {
break
}
contentTokens = append(contentTokens, token)
}
if len(contentTokens) == 0 {
return 0, false
}
return len(contentTokens) + 1, true
}
func (p *TagParser) Parse(tokens []*tokenizer.Token) (ast.Node, error) {
size, ok := p.Match(tokens)
if size == 0 || !ok {
return nil, errors.New("not matched")
}
contentTokens := tokens[1:size]
return &ast.Tag{
Content: tokenizer.Stringify(contentTokens),
}, nil
}
-44
View File
@@ -1,44 +0,0 @@
package parser
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
)
func TestTagParser(t *testing.T) {
tests := []struct {
text string
tag ast.Node
}{
{
text: "*Hello world",
tag: nil,
},
{
text: "# Hello World",
tag: nil,
},
{
text: "#tag",
tag: &ast.Tag{
Content: "tag",
},
},
{
text: "#tag/subtag 123",
tag: &ast.Tag{
Content: "tag/subtag",
},
},
}
for _, test := range tests {
tokens := tokenizer.Tokenize(test.text)
node, _ := NewTagParser().Parse(tokens)
require.Equal(t, StringifyNodes([]ast.Node{test.tag}), StringifyNodes([]ast.Node{node}))
}
}

Some files were not shown because too many files have changed in this diff Show More