Compare commits

...

111 Commits

Author SHA1 Message Date
26a267b111 Editable content rating for every community moderator 2025-04-05 12:06:11 +08:00
1de0a86074 Referenced external attachment 2025-04-05 11:51:35 +08:00
048535d1c0 🐛 Fix analyzer did not clear cache 2025-04-05 11:40:33 +08:00
11d54c7c78 List attachments now return with userinfo 2025-03-31 00:45:39 +08:00
370ee84b34 🛂 Authorized required to access large file 2025-03-30 22:29:57 +08:00
35e4f9a9ad 🐛 Fix image proxy doesn't work 2025-03-30 21:57:30 +08:00
f306ee2e1e Image proxy preview 2025-03-30 21:28:24 +08:00
820d7a9f42 🚚 Expose the models to public 2025-03-29 22:17:22 +08:00
8f91649d25 DirectAccess in filekit 2025-03-29 17:56:38 +08:00
aa52aa73fe Remove useless cache tags 2025-03-29 17:08:17 +08:00
a12ba060f5 ⬆️ Upgrade nexus to fix panic 2025-03-29 16:01:20 +08:00
db0f679a01 Clean up go mod 2025-03-29 15:51:37 +08:00
073b32aa73 ♻️ Rebuilt cache with nexus cache 2025-03-29 15:48:04 +08:00
1390f26afa ♻️ Refactor get s3 client process 2025-03-24 22:48:43 +08:00
9360d17706 Auto to set bucket lookup method 2025-03-24 22:46:23 +08:00
1a1b9eb556 Allow to set preferred storage destination 2025-03-24 21:39:21 +08:00
cd7d19db68 👽 Support Multi-currency 2025-03-23 18:00:27 +08:00
2d4034d7f8 🐛 Try to optimize the uploading 2025-03-23 02:38:20 +08:00
347b1bad77 🐛 Fix large JWT headers 2025-03-23 00:08:32 +08:00
2b87b1db01 ⬆️ Upgrade passport 2025-03-15 17:25:31 +08:00
24ce52902c ⚗️ Disable auto clean up unused attachments for testing 2025-03-11 00:15:51 +08:00
1d866f317f Development SDK of Paperclip
 Support update usage by random id
2025-03-10 23:11:12 +08:00
38aa06cc00 Unused attachment cleanup 2025-03-10 22:30:22 +08:00
e3ca50c4ae Update usage grpc call 2025-03-10 22:15:59 +08:00
69be460e13 🐛 Fix deletion handle 2025-03-10 21:43:43 +08:00
070fd7bb6a 👽 Update the account deletion 2025-03-01 14:07:19 +08:00
10f75ab37a Support webp and apng analyze 2025-02-24 21:15:14 +08:00
671b3fa5a3 🐛 Fix wrong way to determine own pack or not 2025-02-23 13:07:34 +08:00
333aee6b45 Get stickers when get sticker pack 2025-02-23 12:54:27 +08:00
b7a06fabb3 🐛 Fix sticker pack api stacking issue 2025-02-23 12:20:07 +08:00
1dae405232 Sticker pack ownership 2025-02-23 12:02:55 +08:00
cfd1735aee 🐛 Fix panic 2025-02-22 12:34:40 +08:00
0913521c13 🐛 Fix count uploaded bytes incorrectly 2025-02-20 21:08:44 +08:00
aa0f4d292e Billing status displaying 2025-02-18 23:18:38 +08:00
8b02381392 ♻️ Update billing algorithm 2025-02-18 23:12:51 +08:00
22070a464b 🐛 Fix did not preload pack when listing 2025-02-04 02:21:49 +08:00
9ea1012f19 Configurable discount size 2025-01-29 19:14:37 +08:00
80739eab52 Pay for upload 2025-01-29 19:12:54 +08:00
96f429e1d9 🔊 Verbose grpc logging 2025-01-26 23:55:51 +08:00
f0d6cb9499 🔊 Add verbose logging 2025-01-26 21:35:47 +08:00
8b9479833e 🐛 Fix grpc endpoint 2025-01-26 20:45:22 +08:00
0dcbd90f1e :hammer Optimize dockerfile 2025-01-25 00:29:09 +08:00
7c3334a57a Filter grpc endpoints with user 2025-01-24 17:21:28 +08:00
2f256a4c3e 🐛 Fix compile errors 2025-01-24 00:58:39 +08:00
143afdb885 Internal grpc apis 2025-01-24 00:54:16 +08:00
1a9e670d3d 🐛 Fix analyzer again... 2025-01-21 20:07:49 +08:00
4cecbd8e4b 🐛 Fix analyzer calculating ratio 2025-01-21 19:55:41 +08:00
ed9b1474fb 🐛 Fix analyzer panic 2025-01-21 19:11:40 +08:00
ac347540b8 🐛 Analyzer now can handle EXIF Orientation 2025-01-21 12:27:30 +08:00
d0d14d7c67 🐛 Fix analyzer did not save the hash code 2025-01-19 01:44:47 +08:00
8bcf02fa5e Able to directly open (get the image) of a sticker 2025-01-06 21:51:26 +08:00
8cd1037759 🐛 Fix analyze result did not applied 2025-01-01 17:20:30 +08:00
5e73d9acd4 ♻️ Use update api instead of overhaul in background tasks 2025-01-01 11:43:54 +08:00
2bd8dc17d1 🐛 Fix analyze now query issue 2024-12-29 23:19:26 +08:00
78e554577e 🐛 Prevent user from creating boost on same destination 2024-12-29 12:24:56 +08:00
a58f44d50e 🐛 Only apply active boost 2024-12-29 12:23:41 +08:00
c6944cd3df 🐛 Bug fixes 2024-12-29 02:13:17 +08:00
2f1385676b :typo: Fix config typo 2024-12-29 02:13:08 +08:00
af8d665418 🐛 Fix opener get wrong dest 2024-12-29 02:08:41 +08:00
1955d94414 🐛 Fix on boost reupload 2024-12-29 01:59:57 +08:00
0e0ad9c261 🐛 Fix more older cache issue 2024-12-29 01:58:26 +08:00
d979f85f68 🐛 Fix re-upload still need in temp 2024-12-29 01:44:54 +08:00
00fddfdef9 Manually active boost api 2024-12-29 01:38:48 +08:00
3c9b826ed2 🔊 Verbose logging on active boost 2024-12-29 01:36:52 +08:00
b7bedb8dfc Index in list destination api 2024-12-29 01:20:56 +08:00
e89f149336 🐛 Fix cache issue 2024-12-29 01:03:22 +08:00
b041ce3e06 🐛 Fix attachment did not updated 2024-12-29 00:50:04 +08:00
3eee9755d4 Fix listing destinations api 2024-12-29 00:21:29 +08:00
9f0bce81ce 🐛 Fix infinite loading 2024-12-29 00:15:59 +08:00
3a00a06541 🐛 Fix destination loading 2024-12-29 00:03:00 +08:00
cd0141f5b1 🐛 Fix panic 2024-12-28 23:56:02 +08:00
5749b5bfb1 🐛 Fix map routes panic 2024-12-28 23:51:17 +08:00
90fb9960cc Support pre-signed url 2024-12-28 23:16:07 +08:00
ec0444b35c Only can create boost on enabled destinations 2024-12-28 23:12:19 +08:00
49a8159e35 ♻️ Refactored opener
 Support boost in opener
2024-12-28 23:10:57 +08:00
8888f7661a Able to list destinations 2024-12-28 22:27:39 +08:00
ebc3a6f09c List boost by users 2024-12-28 22:14:30 +08:00
1a5787d3c2 List boosts by attachment 2024-12-28 22:08:45 +08:00
b73f5e1912 🎨 Re-orgnize api index 2024-12-28 22:05:05 +08:00
d59966a03e Boost CRUD API 2024-12-28 21:41:13 +08:00
edbe412f97 Casedase deleting attachment 2024-12-28 20:08:32 +08:00
a4e81cabec 🐛 Bug fixes 2024-12-28 19:26:46 +08:00
626fe47bb4 🐛 Fix cannot identify the multipart is completed upload 2024-12-28 16:54:30 +08:00
e297b1b4ea 🐛 Fix backward compability 2024-12-28 16:37:23 +08:00
0c33c2d86c Preload thumbnail, compressed 2024-12-28 15:45:31 +08:00
845894f12c ♻️ Updated way to setting thumbnail & compressed 2024-12-28 15:42:19 +08:00
dda85eae98 🚚 Move io related functions to fs internal package 2024-12-28 14:07:53 +08:00
99dd7f55e0 Fragment uploading continue 2024-12-28 13:56:25 +08:00
30097256b6 🐛 Bug fixes on fragment based uploading 2024-12-28 13:38:55 +08:00
1c8454a658 💥 Change multipart upload API to fragment one 2024-12-28 13:32:08 +08:00
957a9b64b7 ♻️ Fragment based multipart upload API 2024-12-28 13:31:31 +08:00
044b0eefca Able to analyze right now 2024-12-26 22:55:16 +08:00
790b123410 🐛 Fix backward compability 2024-12-26 22:41:11 +08:00
303f8a36f1 Able to edit thumbnail on update meta api 2024-12-26 22:35:39 +08:00
243dbfa9c2 🐛 Fix is indexable query filter issue 2024-12-26 22:11:34 +08:00
3a77af23cb ♻️ Usermeta, is indexable, thumbnail and more fields on attachments 2024-12-26 21:53:09 +08:00
5bbf13b01e 🐛 Fix main file 2024-12-25 23:54:54 +08:00
66ee6c37f2 🐛 Fix delete file load config issue 2024-12-25 23:52:54 +08:00
ae6815b2a4 🐛 Trying fix deleting attachment issue 2024-12-25 23:52:00 +08:00
411b2f3313 🗑️ Remove some files in repo 2024-12-14 12:40:20 +08:00
041bdc6c4d 🐛 Bug fixes on re-uploader 2024-11-10 01:04:07 +08:00
9ed9e4d510 🐛 Fix attachment uploader 2024-11-10 01:00:54 +08:00
d0927b5528 🐛 Fix check reupload condition 2024-11-10 00:59:20 +08:00
6cda5751df 🐛 Still reading the abandoned temporary dest config 2024-11-10 00:50:47 +08:00
c41f8b77ce 🐛 Provide a way to bypass nexus and serving content to prevent nexus reverse proxy issue 2024-11-06 23:01:29 +08:00
2f99ac7b6e 💥 Simplified api of multipart uploading 2024-11-05 20:56:53 +08:00
4223eb6ecd 🐛 Fix hash bug 2024-11-03 11:40:21 +08:00
3f9df23491 🐛 Bug fixes 2024-11-03 02:14:56 +08:00
2d119826fe 🗑️ Remove account 2024-11-03 01:53:57 +08:00
94b777a3e4 🚚 Move from hydrogen to hypernet 2024-11-02 10:41:31 +08:00
2e32de4716 ♻️ Migrated to nexus 2024-10-27 13:13:40 +08:00
65 changed files with 3667 additions and 6671 deletions

2
.gitignore vendored
View File

@@ -1,4 +1,6 @@
/uploads
/dist
/keys
.DS_Store
.idea

9
.idea/Paperclip.iml generated
View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="dataSourceStorageLocal" created-in="GO-242.23339.24">
<data-source name="hy_paperclip@localhost" uuid="bac83d8a-c619-4680-a07f-6674b93fbfea">
<database-info product="PostgreSQL" version="16.3 (Homebrew)" jdbc-version="4.2" driver-name="PostgreSQL JDBC Driver" driver-version="42.6.0" dbms="POSTGRES" exact-version="16.3" exact-driver-version="42.6">
<identifier-quote-string>&quot;</identifier-quote-string>
</database-info>
<case-sensitivity plain-identifiers="lower" quoted-identifiers="exact" />
<secret-storage>master_key</secret-storage>
<user-name>postgres</user-name>
<schema-mapping>
<introspection-scope>
<node kind="database" qname="@">
<node kind="schema" qname="@" />
</node>
</introspection-scope>
</schema-mapping>
</data-source>
</component>
</project>

17
.idea/dataSources.xml generated
View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="hy_paperclip@localhost" uuid="bac83d8a-c619-4680-a07f-6674b93fbfea">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://localhost:5432/hy_paperclip</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +0,0 @@
#n:public
!<md> [10537, 0, null, null, -2147483648, -2147483648]

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Paperclip.iml" filepath="$PROJECT_DIR$/.idea/Paperclip.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

149
.idea/workspace.xml generated
View File

@@ -1,149 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="ALL" />
</component>
<component name="ChangeListManager">
<list default="true" id="18dd0d68-b4b8-40db-9734-9119b5c848bd" name="更改" comment=":bug: Fix bug crash caused by hash small files">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Go File" />
</list>
</option>
</component>
<component name="GOROOT" url="file:///opt/homebrew/opt/go/libexec" />
<component name="Git.Settings">
<option name="RECENT_BRANCH_BY_REPOSITORY">
<map>
<entry key="$PROJECT_DIR$" value="master" />
</map>
</option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProblemsViewState">
<option name="selectedTabId" value="CurrentFile" />
</component>
<component name="ProjectColorInfo">{
&quot;customColor&quot;: &quot;&quot;,
&quot;associatedIndex&quot;: 7
}</component>
<component name="ProjectId" id="2gauyxHu1OWsYigauXZCcaIhfso" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;DefaultGoTemplateProperty&quot;: &quot;Go File&quot;,
&quot;Go Build.Backend.executor&quot;: &quot;Run&quot;,
&quot;Go 构建.Backend.executor&quot;: &quot;Run&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.go.formatter.settings.were.checked&quot;: &quot;true&quot;,
&quot;RunOnceActivity.go.migrated.go.modules.settings&quot;: &quot;true&quot;,
&quot;RunOnceActivity.go.modules.automatic.dependencies.download&quot;: &quot;true&quot;,
&quot;RunOnceActivity.go.modules.go.list.on.any.changes.was.set&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;master&quot;,
&quot;go.import.settings.migrated&quot;: &quot;true&quot;,
&quot;go.sdk.automatically.set&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;/Users/littlesheep/Documents/Projects/Hydrogen/Paperclip/pkg/internal/grpc&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;run.code.analysis.last.selected.profile&quot;: &quot;pProject Default&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.pluginManager&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
},
&quot;keyToStringList&quot;: {
&quot;DatabaseDriversLRU&quot;: [
&quot;postgresql&quot;
]
}
}</component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/pkg/internal/grpc" />
<recent name="$PROJECT_DIR$/pkg/internal/gap" />
<recent name="$PROJECT_DIR$/pkg/internal" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/pkg/internal/server/exts" />
<recent name="$PROJECT_DIR$/pkg/internal/server/api" />
<recent name="$PROJECT_DIR$/pkg" />
<recent name="$PROJECT_DIR$/pkg/internal" />
</key>
</component>
<component name="RunManager">
<configuration name="Backend" type="GoApplicationRunConfiguration" factoryName="Go Application">
<module name="Paperclip" />
<working_directory value="$PROJECT_DIR$" />
<kind value="FILE" />
<package value="git.solsynth.dev/hydrogen/paperclip" />
<directory value="$PROJECT_DIR$" />
<filePath value="$PROJECT_DIR$/pkg/main.go" />
<output_directory value="$PROJECT_DIR$/dist" />
<method v="2" />
</configuration>
</component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-gosdk-5df93f7ad4aa-df9ad98b711f-org.jetbrains.plugins.go.sharedIndexes.bundled-GO-242.23339.24" />
<option value="bundled-js-predefined-d6986cc7102b-5c90d61e3bab-JavaScript-GO-242.23339.24" />
</set>
</attachedChunks>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="应用程序级" UseSingleDictionary="true" transferred="true" />
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value=":bug: Fix migration issue on pools" />
<MESSAGE value=":bug: Fix schedule deletion will delete referenced file" />
<MESSAGE value=":bug: Fix sql statement" />
<MESSAGE value=":bug: Fix doesn't get has lifecycle settings buckets correctly" />
<MESSAGE value=":zap: Fix the RandString method cause the lag" />
<MESSAGE value=":bug: Fix mark clean required issue" />
<MESSAGE value=":sparkles: Support use rid to get file" />
<MESSAGE value=":boom: Replace attachment id by rid when fetching" />
<MESSAGE value=":boom: Use attachment rid instead of primary key when create" />
<MESSAGE value=":bug: Fix crash on maintain cache" />
<MESSAGE value=":sparkles: Un-public indexable &amp; select by pools" />
<MESSAGE value=":sparkles: Multipart file upload" />
<MESSAGE value=":sparkles: Make it more implementable" />
<MESSAGE value=":bug: Fix merged file isn't add to cache" />
<MESSAGE value=":bug: Fix analyzer didn't cache attachment" />
<MESSAGE value=":bug: Fix direct upload will save as non-uploaded" />
<MESSAGE value=":bug: Fix uploader still using old cache api" />
<MESSAGE value=":bug: Fix concurrent upload multipart cause incomplete" />
<MESSAGE value=":bug: Fix account_id where clause causing indexing issue" />
<MESSAGE value=":sparkles: Attachment API can edit metadata" />
<MESSAGE value=":lock: Fix Attachment will contains GPS information" />
<MESSAGE value=":sparkles: Save EXIF into file metadata" />
<MESSAGE value=":zap: Use exif whitelist to prevent produce garbage data" />
<MESSAGE value=":bug: Trying to prevent exiftool causing analyze failed" />
<MESSAGE value=":bug: Fix bug crash caused by hash small files" />
<option name="LAST_COMMIT_MESSAGE" value=":bug: Fix bug crash caused by hash small files" />
</component>
<component name="VgoProject">
<settings-migrated>true</settings-migrated>
</component>
</project>

View File

@@ -10,7 +10,8 @@ FROM golang:alpine
COPY --from=paperclip-server /dist /paperclip/server
RUN apk add --no-cache ffmpeg exiftool
RUN apk add ffmpeg
RUN apk add exiftool
EXPOSE 8445

View File

@@ -1,6 +1,6 @@
# Hydrogen.Paperclip
# Hypernet.Paperclip
Paperclip is the unified attachment service for all hydrogen services.
Paperclip is the unified attachment service for all hypernet services.
It contains file metadata compute, instant upload, calculating hashing, multi destination, media info and more features!
## Features

129
go.mod
View File

@@ -1,115 +1,104 @@
module git.solsynth.dev/hydrogen/paperclip
module git.solsynth.dev/hypernet/paperclip
go 1.22
toolchain go1.23.1
go 1.23.2
require (
git.solsynth.dev/hydrogen/dealer v0.0.0-20240919131945-00c52eba6827
github.com/dgraph-io/ristretto v0.1.1
github.com/eko/gocache/lib/v4 v4.1.6
git.solsynth.dev/hypernet/nexus v0.0.0-20250329075932-d5422ab5b04c
git.solsynth.dev/hypernet/passport v0.0.0-20250315083747-32e91e26013c
git.solsynth.dev/hypernet/wallet v0.0.0-20250323095812-468cd655f886
github.com/barasher/go-exiftool v1.10.0
github.com/dgraph-io/ristretto v0.2.0
github.com/eko/gocache/lib/v4 v4.2.0
github.com/eko/gocache/store/ristretto/v4 v4.2.2
github.com/go-playground/validator/v10 v10.17.0
github.com/gofiber/fiber/v2 v2.52.4
github.com/fatih/color v1.18.0
github.com/go-playground/validator/v10 v10.22.1
github.com/gofiber/fiber/v2 v2.52.6
github.com/google/uuid v1.6.0
github.com/json-iterator/go v1.1.12
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213
github.com/kettek/apng v0.0.0-20220823221153-ff692776a607
github.com/minio/minio-go/v7 v7.0.70
github.com/robfig/cron/v3 v3.0.1
github.com/rs/zerolog v1.31.0
github.com/samber/lo v1.39.0
github.com/rs/zerolog v1.33.0
github.com/samber/lo v1.47.0
github.com/schollz/progressbar/v3 v3.14.4
github.com/spf13/viper v1.18.2
google.golang.org/grpc v1.65.0
github.com/spf13/cast v1.7.0
github.com/spf13/viper v1.19.0
golang.org/x/image v0.0.0-20190802002840-cff245a6509b
google.golang.org/grpc v1.70.0
google.golang.org/protobuf v1.36.4
gopkg.in/vansante/go-ffprobe.v2 v2.2.0
gorm.io/datatypes v1.2.0
gorm.io/driver/postgres v1.5.4
gorm.io/gorm v1.25.6
gorm.io/datatypes v1.2.4
gorm.io/driver/postgres v1.5.9
gorm.io/gorm v1.25.12
)
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/barasher/go-exiftool v1.10.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
git.solsynth.dev/hypernet/pusher v0.0.0-20250216145944-5fb769823a88 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/go-playground/form v3.1.4+incompatible // indirect
github.com/eko/gocache/store/redis/v4 v4.2.2 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/glog v1.2.2 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/hashicorp/consul/api v1.29.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/serf v0.10.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
github.com/jackc/pgx/v5 v5.5.1 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.1 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mbobakov/grpc-consul-resolver v1.5.3 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/philhofer/fwd v1.1.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/prometheus/client_golang v1.19.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.52.3 // indirect
github.com/prometheus/procfs v0.13.0 // indirect
github.com/redis/go-redis/v9 v9.7.3 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/locafero v0.6.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tinylib/msgp v1.1.8 // indirect
github.com/tinylib/msgp v1.2.5 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.52.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/valyala/fasthttp v1.59.0 // indirect
go.uber.org/mock v0.4.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/term v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d // indirect
google.golang.org/protobuf v1.34.2 // indirect
golang.org/x/crypto v0.33.0 // indirect
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/mysql v1.5.2 // indirect
gorm.io/driver/mysql v1.5.7 // indirect
)

814
go.sum
View File

@@ -1,834 +1,296 @@
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=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
git.solsynth.dev/hydrogen/dealer v0.0.0-20240919131945-00c52eba6827 h1:1ACMPm2ArRpVNYrND/y/R6oPiuMfKe49fP+lG3mcNug=
git.solsynth.dev/hydrogen/dealer v0.0.0-20240919131945-00c52eba6827/go.mod h1:Q51JPkKnV0UoOT/IRmdBh5CyfSlp7s8BRGzgooYHqkI=
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/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
git.solsynth.dev/hypernet/nexus v0.0.0-20250329072729-4a08fd8f1c46 h1:oH2jq7ZG5cslCULUMWqv4dS/YNvd+Xcuv4rBPj0uGA8=
git.solsynth.dev/hypernet/nexus v0.0.0-20250329072729-4a08fd8f1c46/go.mod h1:5tk62VQ1DcbR0EAN2jAOqYxHiegUPEC805JlfQ/G19I=
git.solsynth.dev/hypernet/nexus v0.0.0-20250329075932-d5422ab5b04c h1:XgdTgJxSAQuCbiG15hN5pY6chzcz8sX3Onm2itS+Ufs=
git.solsynth.dev/hypernet/nexus v0.0.0-20250329075932-d5422ab5b04c/go.mod h1:5tk62VQ1DcbR0EAN2jAOqYxHiegUPEC805JlfQ/G19I=
git.solsynth.dev/hypernet/passport v0.0.0-20250315083747-32e91e26013c h1:XB8EBX34WB2skmjaVFot5IlxKF2qFZ2SueG/Y9SiJ6Y=
git.solsynth.dev/hypernet/passport v0.0.0-20250315083747-32e91e26013c/go.mod h1:k7MZQWYBpxlk3g9bx0HTh5C3m+MG/wr0hAiRM/VyAqs=
git.solsynth.dev/hypernet/pusher v0.0.0-20250216145944-5fb769823a88 h1:2HEENe9KUrdaJeNBzx9lsuXQGyzWqCgnLTKQnr8xFr8=
git.solsynth.dev/hypernet/pusher v0.0.0-20250216145944-5fb769823a88/go.mod h1:ildzMtLagNsLK0Rkw4Hgk2TrrwqZnjwJIUx0MNZwcDY=
git.solsynth.dev/hypernet/wallet v0.0.0-20250323095812-468cd655f886 h1:rVssXF8jZ64ctAfzlCgIgF22NCT9VAPAVxrwlcItx3s=
git.solsynth.dev/hypernet/wallet v0.0.0-20250323095812-468cd655f886/go.mod h1:rmomNGQ6RBSp8TpZGA8tFr5M54AL2NADJ/1n0MfrIRM=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/barasher/go-exiftool v1.10.0 h1:f5JY5jc42M7tzR6tbL9508S2IXdIcG9QyieEXNMpIhs=
github.com/barasher/go-exiftool v1.10.0/go.mod h1:F9s/a3uHSM8YniVfwF+sbQUtP8Gmh9nyzigNF+8vsWo=
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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
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/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
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=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
github.com/dgraph-io/ristretto v1.0.0 h1:SYG07bONKMlFDUYu5pEu3DGAh8c2OFNzKm6G9J4Si84=
github.com/dgraph-io/ristretto v1.0.0/go.mod h1:jTi2FiYEhQ1NsMmA7DeBykizjOuY88NhKBkepyu1jPc=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
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=
github.com/eko/gocache/lib/v4 v4.1.6 h1:5WWIGISKhE7mfkyF+SJyWwqa4Dp2mkdX8QsZpnENqJI=
github.com/eko/gocache/lib/v4 v4.1.6/go.mod h1:HFxC8IiG2WeRotg09xEnPD72sCheJiTSr4Li5Ameg7g=
github.com/eko/gocache/lib/v4 v4.2.0 h1:MNykyi5Xw+5Wu3+PUrvtOCaKSZM1nUSVftbzmeC7Yuw=
github.com/eko/gocache/lib/v4 v4.2.0/go.mod h1:7ViVmbU+CzDHzRpmB4SXKyyzyuJ8A3UW3/cszpcqB4M=
github.com/eko/gocache/store/redis/v4 v4.2.2 h1:Thw31fzGuH3WzJywsdbMivOmP550D6JS7GDHhvCJPA0=
github.com/eko/gocache/store/redis/v4 v4.2.2/go.mod h1:LaTxLKx9TG/YUEybQvPMij++D7PBTIJ4+pzvk0ykz0w=
github.com/eko/gocache/store/ristretto/v4 v4.2.2 h1:lXFzoZ5ck6Gy6ON7f5DHSkNt122qN7KoroCVgVwF7oo=
github.com/eko/gocache/store/ristretto/v4 v4.2.2/go.mod h1:uIvBVJzqRepr5L0RsbkfQ2iYfbyos2fuji/s4yM+aUM=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
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/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
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/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
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-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/form v3.1.4+incompatible h1:lvKiHVxE2WvzDIoyMnWcjyiBxKt2+uFJyZcPYWsLnjI=
github.com/go-playground/form v3.1.4+incompatible/go.mod h1:lhcKXfTuhRtIZCIKUeJ0b5F207aeQCPbZU09ScKjwWg=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74=
github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
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/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM=
github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
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-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4=
github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY=
github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
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.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
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/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
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.4.1/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.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/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/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/hashicorp/consul/api v1.29.1 h1:UEwOjYJrd3lG1x5w7HxDRMGiAUPrb3f103EoeKuuEcc=
github.com/hashicorp/consul/api v1.29.1/go.mod h1:lumfRkY/coLuqMICkI7Fh3ylMG31mQSRZyef2c5YvJI=
github.com/hashicorp/consul/proto-public v0.6.1 h1:+uzH3olCrksXYWAYHKqK782CtK9scfqH+Unlw3UHhCg=
github.com/hashicorp/consul/proto-public v0.6.1/go.mod h1:cXXbOg74KBNGajC+o8RlA502Esf0R9prcoJgiOX/2Tg=
github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg=
github.com/hashicorp/consul/sdk v0.16.1/go.mod h1:fSXvwxB2hmh1FMZCNl6PwX0Q/1wdWtHJcZ7Ea5tns0s=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
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 v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
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=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.1 h1:5I9etrGkLrN+2XPCsi6XLlV5DITbSL/xBZdmAxFcXPI=
github.com/jackc/pgx/v5 v5.5.1/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
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.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
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/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kettek/apng v0.0.0-20220823221153-ff692776a607 h1:8tP9cdXzcGX2AvweVVG/lxbI7BSjWbNNUustwJ9dQVA=
github.com/kettek/apng v0.0.0-20220823221153-ff692776a607/go.mod h1:x78/VRQYKuCftMWS0uK5e+F5RJ7S4gSlESRWI0Prl6Q=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
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.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/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mbobakov/grpc-consul-resolver v1.5.3 h1:xL7nJm8qCvxgHMqlnF4naXruBUoHqfUWORl3UmwKByU=
github.com/mbobakov/grpc-consul-resolver v1.5.3/go.mod h1:0wN8+McBocuk5mO9xlAfrmBSothm7sps43bFGubg0m4=
github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE=
github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.70 h1:1u9NtMgfK1U42kUxcsl5v0yj6TEOPR497OAQxpJnn2g=
github.com/minio/minio-go/v7 v7.0.70/go.mod h1:4yBA8v80xGA30cfM3fz0DKYMXunWl/AV/6tWEs9ryzo=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.52.3 h1:5f8uj6ZwHSscOGNdIQg6OiZv/ybiK2CO2q2drVZAQSA=
github.com/prometheus/common v0.52.3/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=
github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o=
github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
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/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/schollz/progressbar/v3 v3.14.4 h1:W9ZrDSJk7eqmQhd3uxFNNcTr0QL+xuGNI9dEMrw0r74=
github.com/schollz/progressbar/v3 v3.14.4/go.mod h1:aT3UQ7yGm+2ZjeXPqsjTenwL3ddUiuZ0kfQ/2tHlyNI=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
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/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
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.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
github.com/spf13/viper v1.18.2/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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
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 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
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.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
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/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
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/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0=
github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI=
github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
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-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
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=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
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.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
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-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/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-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
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.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
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/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.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=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/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-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/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-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.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/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/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-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/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.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
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=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
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-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d h1:JU0iKnSg02Gmb5ZdV8nYsKEKsP6o/FGVWTrw4i1DA9A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
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=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
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.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=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 h1:J1H9f+LEdWAfHcez/4cvaVBox7cOYT+IU6rgqj5x++8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk=
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/vansante/go-ffprobe.v2 v2.2.0 h1:iuOqTsbfYuqIz4tAU9NWh22CmBGxlGHdgj4iqP+NUmY=
gopkg.in/vansante/go-ffprobe.v2 v2.2.0/go.mod h1:qF0AlAjk7Nqzqf3y333Ly+KxN3cKF2JqA3JT5ZheUGE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/datatypes v1.2.0 h1:5YT+eokWdIxhJgWHdrb2zYUimyk0+TaFth+7a0ybzco=
gorm.io/datatypes v1.2.0/go.mod h1:o1dh0ZvjIjhH/bngTpypG6lVRJ5chTBxE09FH/71k04=
gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs=
gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8=
gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo=
gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0=
gorm.io/datatypes v1.2.4 h1:uZmGAcK/QZ0uyfCuVg0VQY1ZmV9h1fuG0tMwKByO1z4=
gorm.io/datatypes v1.2.4/go.mod h1:f4BsLcFAX67szSv8svwLRjklArSHAvHLeE3pXAS5DZI=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0=
gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig=
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.6 h1:V92+vVda1wEISSOMtodHVRcUIOPYa2tgQtyF+DfFx+A=
gorm.io/gorm v1.25.6/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/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=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=

132
pkg/filekit/io.go Normal file
View File

@@ -0,0 +1,132 @@
package filekit
import (
"context"
"time"
"git.solsynth.dev/hypernet/nexus/pkg/nex"
"git.solsynth.dev/hypernet/nexus/pkg/nex/cachekit"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/proto"
"github.com/goccy/go-json"
)
func GetAttachment(c *nex.Conn, rid string) (models.Attachment, error) {
cacheConn, err := cachekit.NewConn(c, 3*time.Second)
if err == nil {
key := cachekit.FKey(cachekit.DAAttachment, rid)
if attachment, err := cachekit.Get[models.Attachment](cacheConn, key); err == nil {
return attachment, nil
}
}
var attachment models.Attachment
conn, err := c.GetClientGrpcConn("uc")
if err != nil {
return attachment, nil
}
pc := proto.NewAttachmentServiceClient(conn)
resp, err := pc.GetAttachment(context.Background(), &proto.GetAttachmentRequest{
Rid: &rid,
})
if err != nil {
return attachment, err
}
if err := json.Unmarshal(resp.Attachment, &attachment); err != nil {
return attachment, err
}
return attachment, nil
}
func ListAttachment(c *nex.Conn, rid []string) ([]models.Attachment, error) {
var attachments []models.Attachment
var missingRid []string
cachedAttachments := make(map[string]models.Attachment)
// Try to get attachments from cache
cacheConn, err := cachekit.NewConn(c, 3*time.Second)
if err == nil {
for _, rid := range rid {
key := cachekit.FKey(cachekit.DAAttachment, rid)
if attachment, err := cachekit.Get[models.Attachment](cacheConn, key); err == nil {
cachedAttachments[rid] = attachment
} else {
missingRid = append(missingRid, rid)
}
}
}
// If all attachments are found in cache, return them
if len(missingRid) == 0 {
for _, attachment := range cachedAttachments {
attachments = append(attachments, attachment)
}
return attachments, nil
}
// Fetch missing attachments from the gRPC service
conn, err := c.GetClientGrpcConn("uc")
if err != nil {
return attachments, err
}
pc := proto.NewAttachmentServiceClient(conn)
resp, err := pc.ListAttachment(context.Background(), &proto.ListAttachmentRequest{
Rid: missingRid,
})
if err != nil {
return attachments, err
}
// Parse the fetched attachments
for _, item := range resp.GetAttachments() {
var attachment models.Attachment
if err := json.Unmarshal(item, &attachment); err != nil {
return attachments, err
}
attachments = append(attachments, attachment)
}
// Merge cached and fetched results
for _, attachment := range cachedAttachments {
attachments = append(attachments, attachment)
}
return attachments, nil
}
func UpdateVisibility(c *nex.Conn, request *proto.UpdateVisibilityRequest) error {
conn, err := c.GetClientGrpcConn("uc")
if err != nil {
return nil
}
pc := proto.NewAttachmentServiceClient(conn)
_, err = pc.UpdateVisibility(context.Background(), request)
return err
}
func DeleteAttachment(c *nex.Conn, request *proto.DeleteAttachmentRequest) error {
conn, err := c.GetClientGrpcConn("uc")
if err != nil {
return nil
}
pc := proto.NewAttachmentServiceClient(conn)
_, err = pc.DeleteAttachment(context.Background(), request)
return err
}
func CountAttachmentUsage(c *nex.Conn, request *proto.UpdateUsageRequest) error {
conn, err := c.GetClientGrpcConn("uc")
if err != nil {
return nil
}
pc := proto.NewAttachmentServiceClient(conn)
_, err = pc.UpdateUsage(context.Background(), request)
return err
}

View File

@@ -0,0 +1,142 @@
package models
import (
"time"
"git.solsynth.dev/hypernet/nexus/pkg/nex/cachekit"
"git.solsynth.dev/hypernet/nexus/pkg/nex/cruda"
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/gap"
"gorm.io/datatypes"
"gorm.io/gorm"
)
const (
AttachmentDstExternal = -1 // The destination marked the file did not store inside our service
AttachmentDstTemporary = 0 // Destination 0 is a reserved config for pre-upload processing
)
const (
AttachmentTypeNormal = iota
AttachmentTypeThumbnail
AttachmentTypeCompressed
)
type Attachment struct {
cruda.BaseModel
// Random ID is for accessing (appear in URL)
Rid string `json:"rid" gorm:"uniqueIndex"`
// Unique ID is for storing (appear in local file name or object name)
Uuid string `json:"uuid"`
Size int64 `json:"size"`
Name string `json:"name"`
Alternative string `json:"alt"`
MimeType string `json:"mimetype"`
HashCode string `json:"hash"`
Destination int `json:"destination"`
RefCount int `json:"ref_count"`
Type uint `json:"type"`
CleanedAt *time.Time `json:"cleaned_at"`
Metadata datatypes.JSONMap `json:"metadata"` // This field is analyzer auto generated metadata
Usermeta datatypes.JSONMap `json:"usermeta"` // This field is user set metadata
ContentRating int `json:"content_rating"` // This field use to filter mature content or not
QualityRating int `json:"quality_rating"` // This field use to filter good content or not
IsAnalyzed bool `json:"is_analyzed"`
IsSelfRef bool `json:"is_self_ref"`
IsIndexable bool `json:"is_indexable"` // Show this attachment in the public directory api or not
// Count the usage of this attachment across all services
// If this number remain 0, it will be deleted during the maintenance
UsedCount int `json:"used_count"`
Thumbnail *Attachment `json:"thumbnail"`
ThumbnailID *uint `json:"thumbnail_id"`
Compressed *Attachment `json:"compressed"`
CompressedID *uint `json:"compressed_id"`
Ref *Attachment `json:"ref"`
RefID *uint `json:"ref_id"`
RefURL *string `json:"ref_url"` // External URL for the attachment
Pool *AttachmentPool `json:"pool"`
PoolID *uint `json:"pool_id"`
Boosts []AttachmentBoost `json:"boosts"`
AccountID uint `json:"account_id"`
Account models.Account `gorm:"-" json:"account"`
}
func (v *Attachment) AfterUpdate(tx *gorm.DB) error {
_ = cachekit.Delete(
gap.Ca,
cachekit.FKey(cachekit.DAAttachment, v.Rid),
)
return nil
}
// Data model for in progress multipart attachments
type AttachmentFragment struct {
cruda.BaseModel
// Random ID is for accessing (appear in URL)
Rid string `json:"rid" gorm:"uniqueIndex"`
// Unique ID is for storing (appear in local file name or object name)
Uuid string `json:"uuid"`
Size int64 `json:"size"`
Name string `json:"name"`
Alternative string `json:"alt"`
MimeType string `json:"mimetype"`
HashCode string `json:"hash"`
Fingerprint *string `json:"fingerprint"` // Client side generated hash, for continue uploading
FileChunks datatypes.JSONMap `json:"file_chunks"`
Metadata datatypes.JSONMap `json:"metadata"` // This field is analyzer auto generated metadata
Usermeta datatypes.JSONMap `json:"usermeta"` // This field is user set metadata
Pool *AttachmentPool `json:"pool"`
PoolID *uint `json:"pool_id"`
AccountID uint `json:"account_id"`
FileChunksMissing []string `json:"file_chunks_missing" gorm:"-"` // This field use to prompt client which chunks is pending upload, do not store it
}
func (v *AttachmentFragment) AfterUpdate(tx *gorm.DB) error {
_ = cachekit.Delete(
gap.Ca,
cachekit.FKey("attachment-fragment", v.Rid),
)
return nil
}
func (v *AttachmentFragment) ToAttachment() Attachment {
return Attachment{
Rid: v.Rid,
Uuid: v.Uuid,
Size: v.Size,
Name: v.Name,
Alternative: v.Alternative,
MimeType: v.MimeType,
HashCode: v.HashCode,
Metadata: v.Metadata,
Usermeta: v.Usermeta,
Destination: AttachmentDstTemporary,
Type: AttachmentTypeNormal,
Pool: v.Pool,
PoolID: v.PoolID,
AccountID: v.AccountID,
}
}

View File

@@ -0,0 +1,24 @@
package models
import "git.solsynth.dev/hypernet/nexus/pkg/nex/cruda"
const (
BoostStatusPending = iota
BoostStatusActive
BoostStatusSuspended
BoostStatusError
)
// AttachmentBoost is made for speed up attachment loading by copy the original attachments
// to others faster CDN or storage destinations.
type AttachmentBoost struct {
cruda.BaseModel
Status int `json:"status"`
Destination int `json:"destination"`
AttachmentID uint `json:"attachment_id"`
Attachment Attachment `json:"attachment"`
AccountID uint `json:"account"`
}

View File

@@ -0,0 +1,54 @@
package models
import (
pkg "git.solsynth.dev/hypernet/paperclip/pkg/internal"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
const (
DestinationTypeLocal = "local"
DestinationTypeS3 = "s3"
)
type BaseDestination struct {
ID int `json:"id,omitempty"` // Auto filled with index, only for user
Type string `json:"type"`
Label string `json:"label"`
Region string `json:"region"`
IsBoost bool `json:"is_boost"`
}
type LocalDestination struct {
BaseDestination
Path string `json:"path"`
AccessBaseURL string `json:"access_baseurl"`
}
type S3Destination struct {
BaseDestination
Path string `json:"path"`
Bucket string `json:"bucket"`
Endpoint string `json:"endpoint"`
SecretID string `json:"secret_id"`
SecretKey string `json:"secret_key"`
AccessBaseURL string `json:"access_baseurl"`
ImageProxyURL string `json:"image_proxy_baseurl"`
EnableSSL bool `json:"enable_ssl"`
EnableSigned bool `json:"enable_signed"`
BucketLookup int `json:"bucket_lookup"`
}
func (v S3Destination) GetClient() (*minio.Client, error) {
client, err := minio.New(v.Endpoint, &minio.Options{
Creds: credentials.NewStaticV4(v.SecretID, v.SecretKey, ""),
Secure: v.EnableSSL,
BucketLookup: minio.BucketLookupType(v.BucketLookup),
})
if err == nil {
client.SetAppInfo("HyperNet.Paperclip", pkg.AppVersion)
}
return client, err
}

View File

@@ -1,12 +1,12 @@
package models
import (
"git.solsynth.dev/hydrogen/dealer/pkg/hyper"
"git.solsynth.dev/hypernet/nexus/pkg/nex/cruda"
"gorm.io/datatypes"
)
type AttachmentPool struct {
hyper.BaseModel
cruda.BaseModel
Alias string `json:"alias"`
Name string `json:"name"`
@@ -15,8 +15,7 @@ type AttachmentPool struct {
Attachments []Attachment `json:"attachments" gorm:"foreignKey:PoolID"`
Account *Account `json:"account"`
AccountID *uint `json:"account_id"`
AccountID *uint `json:"account_id"`
}
type AttachmentPoolConfig struct {

View File

@@ -1,9 +1,11 @@
package models
import "git.solsynth.dev/hydrogen/dealer/pkg/hyper"
import (
"git.solsynth.dev/hypernet/nexus/pkg/nex/cruda"
)
type Sticker struct {
hyper.BaseModel
cruda.BaseModel
Alias string `json:"alias"`
Name string `json:"name"`
@@ -12,16 +14,19 @@ type Sticker struct {
PackID uint `json:"pack_id"`
Pack StickerPack `json:"pack"`
AccountID uint `json:"account_id"`
Account Account `json:"account"`
}
type StickerPack struct {
hyper.BaseModel
cruda.BaseModel
Prefix string `json:"prefix"`
Name string `json:"name"`
Description string `json:"description"`
Stickers []Sticker `json:"stickers" gorm:"foreignKey:PackID;constraint:OnDelete:CASCADE"`
AccountID uint `json:"account_id"`
Account Account `json:"account"`
}
type StickerPackOwnership struct {
PackID uint `json:"pack_id" gorm:"primaryKey"`
AccountID uint `json:"account_id" gorm:"primaryKey"`
}

View File

@@ -1,24 +0,0 @@
package cache
import (
"github.com/dgraph-io/ristretto"
"github.com/eko/gocache/lib/v4/store"
ristrettoCache "github.com/eko/gocache/store/ristretto/v4"
)
var S store.StoreInterface
func NewStore() error {
ristretto, err := ristretto.NewCache(&ristretto.Config{
NumCounters: 1000,
MaxCost: 100,
BufferItems: 64,
})
if err != nil {
return err
}
S = ristrettoCache.NewRistretto(ristretto)
return nil
}

View File

@@ -1,16 +1,18 @@
package database
import (
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"gorm.io/gorm"
)
var AutoMaintainRange = []any{
&models.Account{},
&models.AttachmentPool{},
&models.Attachment{},
&models.AttachmentFragment{},
&models.AttachmentBoost{},
&models.StickerPack{},
&models.Sticker{},
&models.StickerPackOwnership{},
}
func RunMigration(source *gorm.DB) error {

View File

@@ -1,24 +1,23 @@
package database
import (
"git.solsynth.dev/hypernet/nexus/pkg/nex/cruda"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/gap"
"github.com/rs/zerolog/log"
"github.com/samber/lo"
"github.com/spf13/viper"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
)
var C *gorm.DB
func NewSource() error {
func NewGorm() error {
var err error
dialector := postgres.Open(viper.GetString("database.dsn"))
C, err = gorm.Open(dialector, &gorm.Config{NamingStrategy: schema.NamingStrategy{
TablePrefix: viper.GetString("database.prefix"),
}, Logger: logger.New(&log.Logger, logger.Config{
dsn, err := cruda.NewCrudaConn(gap.Nx).AllocDatabase("paperclip")
C, err = gorm.Open(postgres.Open(dsn), &gorm.Config{Logger: logger.New(&log.Logger, logger.Config{
Colorful: true,
IgnoreRecordNotFoundError: true,
LogLevel: lo.Ternary(viper.GetBool("debug.database"), logger.Info, logger.Silent),

View File

@@ -0,0 +1,3 @@
# File System
The reason of why this package exists is because "cycle import was not allowed"

View File

@@ -0,0 +1,50 @@
package fs
import (
"context"
"fmt"
"os"
"path/filepath"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
pkg "git.solsynth.dev/hypernet/paperclip/pkg/internal"
jsoniter "github.com/json-iterator/go"
"github.com/minio/minio-go/v7"
"github.com/spf13/viper"
)
func DownloadFileToLocal(meta models.Attachment, dst int) (string, error) {
destMap := viper.GetStringMap(fmt.Sprintf("destinations.%d", dst))
var dest models.BaseDestination
rawDest, _ := jsoniter.Marshal(destMap)
_ = jsoniter.Unmarshal(rawDest, &dest)
switch dest.Type {
case models.DestinationTypeLocal:
var destConfigured models.LocalDestination
_ = jsoniter.Unmarshal(rawDest, &destConfigured)
return filepath.Join(destConfigured.Path, meta.Uuid), nil
case models.DestinationTypeS3:
var destConfigured models.S3Destination
_ = jsoniter.Unmarshal(rawDest, &destConfigured)
client, err := destConfigured.GetClient()
client.SetAppInfo("HyperNet.Paperclip", pkg.AppVersion)
if err != nil {
return "", fmt.Errorf("unable to configure s3 client: %v", err)
}
inDst := filepath.Join(os.TempDir(), meta.Uuid)
err = client.FGetObject(context.Background(), destConfigured.Bucket, meta.Uuid, inDst, minio.GetObjectOptions{})
if err != nil {
return "", fmt.Errorf("unable to upload file to s3: %v", err)
}
return inDst, nil
default:
return "", fmt.Errorf("invalid destination: unsupported protocol %s", dest.Type)
}
}

View File

@@ -1,4 +1,4 @@
package services
package fs
import (
"fmt"
@@ -6,14 +6,16 @@ import (
"os"
"path/filepath"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"github.com/spf13/viper"
)
func MergeFileChunks(meta models.Attachment, arrange []string) (models.Attachment, error) {
func MergeFileChunks(meta models.AttachmentFragment, arrange []string) (models.Attachment, error) {
attachment := meta.ToAttachment()
// Fetch destination from config
destMap := viper.GetStringMapString("destinations.temporary")
destMap := viper.GetStringMapString("destinations.0")
var dest models.LocalDestination
dest.Path = destMap["path"]
@@ -22,7 +24,7 @@ func MergeFileChunks(meta models.Attachment, arrange []string) (models.Attachmen
destPath := filepath.Join(dest.Path, meta.Uuid)
destFile, err := os.Create(destPath)
if err != nil {
return meta, err
return attachment, err
}
defer destFile.Close()
@@ -34,7 +36,7 @@ func MergeFileChunks(meta models.Attachment, arrange []string) (models.Attachmen
chunkPath := filepath.Join(dest.Path, fmt.Sprintf("%s.part%s", meta.Uuid, chunk))
chunkFile, err := os.Open(chunkPath)
if err != nil {
return meta, err
return attachment, err
}
defer chunkFile.Close() // Ensure the file is closed after reading
@@ -42,35 +44,29 @@ func MergeFileChunks(meta models.Attachment, arrange []string) (models.Attachmen
for {
n, err := chunkFile.Read(buf)
if err != nil && err != io.EOF {
return meta, err
return attachment, err
}
if n == 0 {
break
}
if _, err := destFile.Write(buf[:n]); err != nil {
return meta, err
return attachment, err
}
}
}
// Post-upload tasks
meta.IsUploaded = true
meta.FileChunks = nil
if err := database.C.Save(&meta).Error; err != nil {
return meta, err
}
CacheAttachment(meta)
PublishAnalyzeTask(meta)
// Clean up: remove chunk files
go DeleteFragment(meta)
for _, chunk := range arrange {
chunkPath := filepath.Join(dest.Path, fmt.Sprintf("%s.part%s", meta.Uuid, chunk))
if err := os.Remove(chunkPath); err != nil {
return meta, err
return attachment, err
}
}
return meta, nil
// Clean up: remove fragment record
database.C.Delete(&meta)
return attachment, nil
}

View File

@@ -1,40 +1,22 @@
package services
package fs
import (
"context"
"fmt"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
"github.com/samber/lo"
"os"
"path/filepath"
"time"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"github.com/samber/lo"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
jsoniter "github.com/json-iterator/go"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
)
var fileDeletionQueue = make(chan models.Attachment, 256)
func PublishDeleteFileTask(file models.Attachment) {
fileDeletionQueue <- file
}
func StartConsumeDeletionTask() {
for {
task := <-fileDeletionQueue
start := time.Now()
if err := DeleteFile(task); err != nil {
log.Error().Err(err).Any("task", task).Msg("A file deletion task failed...")
} else {
log.Info().Dur("elapsed", time.Since(start)).Uint("id", task.ID).Msg("A file deletion task was completed.")
}
}
}
func RunMarkLifecycleDeletionTask() {
var pools []models.AttachmentPool
if err := database.C.Find(&pools).Error; err != nil {
@@ -96,27 +78,22 @@ func RunScheduleDeletionTask() {
database.C.Where("cleaned_at IS NOT NULL").Delete(&models.Attachment{})
}
func DeleteFragment(meta models.AttachmentFragment) error {
destMap := viper.GetStringMap("destinations.0")
var dest models.LocalDestination
rawDest, _ := jsoniter.Marshal(destMap)
_ = jsoniter.Unmarshal(rawDest, &dest)
for cid := range meta.FileChunks {
path := filepath.Join(dest.Path, fmt.Sprintf("%s.part%s", meta.Uuid, cid))
_ = os.Remove(path)
}
return nil
}
func DeleteFile(meta models.Attachment) error {
if !meta.IsUploaded {
destMap := viper.GetStringMap("destinations.temporary")
var dest models.LocalDestination
rawDest, _ := jsoniter.Marshal(destMap)
_ = jsoniter.Unmarshal(rawDest, &dest)
for cid := range meta.FileChunks {
path := filepath.Join(dest.Path, fmt.Sprintf("%s.part%s", meta.Uuid, cid))
_ = os.Remove(path)
}
return nil
}
var destMap map[string]any
if meta.Destination == models.AttachmentDstTemporary {
destMap = viper.GetStringMap("destinations.temporary")
} else {
destMap = viper.GetStringMap("destinations.permanent")
}
destMap := viper.GetStringMap(fmt.Sprintf("destinations.%d", meta.Destination))
var dest models.BaseDestination
rawDest, _ := jsoniter.Marshal(destMap)
@@ -126,31 +103,28 @@ func DeleteFile(meta models.Attachment) error {
case models.DestinationTypeLocal:
var destConfigured models.LocalDestination
_ = jsoniter.Unmarshal(rawDest, &destConfigured)
return DeleteFileFromLocal(destConfigured, meta)
return DeleteFileFromLocal(destConfigured, meta.Uuid)
case models.DestinationTypeS3:
var destConfigured models.S3Destination
_ = jsoniter.Unmarshal(rawDest, &destConfigured)
return DeleteFileFromS3(destConfigured, meta)
return DeleteFileFromS3(destConfigured, meta.Uuid)
default:
return fmt.Errorf("invalid destination: unsupported protocol %s", dest.Type)
}
}
func DeleteFileFromLocal(config models.LocalDestination, meta models.Attachment) error {
fullpath := filepath.Join(config.Path, meta.Uuid)
func DeleteFileFromLocal(config models.LocalDestination, uuid string) error {
fullpath := filepath.Join(config.Path, uuid)
return os.Remove(fullpath)
}
func DeleteFileFromS3(config models.S3Destination, meta models.Attachment) error {
client, err := minio.New(config.Endpoint, &minio.Options{
Creds: credentials.NewStaticV4(config.SecretID, config.SecretKey, ""),
Secure: config.EnableSSL,
})
func DeleteFileFromS3(config models.S3Destination, uuid string) error {
client, err := config.GetClient()
if err != nil {
return fmt.Errorf("unable to configure s3 client: %v", err)
}
err = client.RemoveObject(context.Background(), config.Bucket, filepath.Join(config.Path, meta.Uuid), minio.RemoveObjectOptions{})
err = client.RemoveObject(context.Background(), config.Bucket, filepath.Join(config.Path, uuid), minio.RemoveObjectOptions{})
if err != nil {
return fmt.Errorf("unable to upload file to s3: %v", err)
}

View File

@@ -1,15 +0,0 @@
package gap
import "net"
func GetOutboundIP() (net.IP, error) {
conn, err := net.Dial("udp", "1.1.1.1:80")
if err != nil {
return nil, err
} else {
defer conn.Close()
}
localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP, nil
}

View File

@@ -2,41 +2,52 @@ package gap
import (
"fmt"
"git.solsynth.dev/hydrogen/dealer/pkg/hyper"
"git.solsynth.dev/hydrogen/dealer/pkg/proto"
"github.com/rs/zerolog/log"
"strings"
"time"
"git.solsynth.dev/hypernet/nexus/pkg/nex"
"git.solsynth.dev/hypernet/nexus/pkg/nex/cachekit"
"git.solsynth.dev/hypernet/nexus/pkg/proto"
"github.com/rs/zerolog/log"
"github.com/samber/lo"
"github.com/spf13/viper"
)
var H *hyper.HyperConn
var (
Nx *nex.Conn
Ca *cachekit.Conn
)
func RegisterService() error {
func InitializeToNexus() error {
grpcBind := strings.SplitN(viper.GetString("grpc_bind"), ":", 2)
httpBind := strings.SplitN(viper.GetString("bind"), ":", 2)
outboundIp, _ := GetOutboundIP()
outboundIp, _ := nex.GetOutboundIP()
grpcOutbound := fmt.Sprintf("%s:%s", outboundIp, grpcBind[1])
httpOutbound := fmt.Sprintf("%s:%s", outboundIp, httpBind[1])
var err error
H, err = hyper.NewHyperConn(viper.GetString("dealer.addr"), &proto.ServiceInfo{
Nx, err = nex.NewNexusConn(viper.GetString("nexus_addr"), &proto.ServiceInfo{
Id: viper.GetString("id"),
Type: hyper.ServiceTypeFileProvider,
Type: "uc",
Label: "Paperclip",
GrpcAddr: grpcOutbound,
HttpAddr: &httpOutbound,
HttpAddr: lo.ToPtr("http://" + httpOutbound + "/api"),
})
if err == nil {
go func() {
err := H.KeepRegisterService()
err := Nx.RunRegistering()
if err != nil {
log.Error().Err(err).Msg("An error occurred while registering service...")
}
}()
}
if Ca, err = cachekit.NewConn(Nx, 3*time.Second); err != nil {
return fmt.Errorf("failed to create cachekit connection: %v", err)
}
return err
}

View File

@@ -0,0 +1,153 @@
package grpc
import (
"context"
"git.solsynth.dev/hypernet/nexus/pkg/nex"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/services"
"git.solsynth.dev/hypernet/paperclip/pkg/proto"
"github.com/rs/zerolog/log"
"github.com/samber/lo"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func (v *Server) GetAttachment(ctx context.Context, request *proto.GetAttachmentRequest) (*proto.GetAttachmentResponse, error) {
tx := database.C
if request.Id != nil {
tx = tx.Where("id = ?", request.Id)
} else if request.Rid != nil {
tx = tx.Where("rid = ?", request.Rid)
} else {
return nil, status.Error(codes.InvalidArgument, "you must provide id or random id")
}
if request.UserId != nil {
tx = tx.Where("account_id = ?", request.UserId)
}
var attachment models.Attachment
if err := tx.First(&attachment).Error; err != nil {
return nil, status.Error(codes.NotFound, "attachment not found")
}
out, err := services.CompleteAttachmentMeta(attachment)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &proto.GetAttachmentResponse{
Attachment: nex.EncodeMap(out[0]),
}, nil
}
func (v *Server) ListAttachment(ctx context.Context, request *proto.ListAttachmentRequest) (*proto.ListAttachmentResponse, error) {
tx := database.C
if len(request.Id) == 0 && len(request.Rid) == 0 {
return nil, status.Error(codes.InvalidArgument, "you must provide at least one id or random id")
}
if len(request.Id) > 0 {
tx = tx.Where("id IN ?", request.Id)
}
if len(request.Rid) > 0 {
tx = tx.Where("rid IN ?", request.Rid)
}
attachments := make([]models.Attachment, 0)
err := tx.Find(&attachments).Error
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
out, err := services.CompleteAttachmentMeta(attachments...)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &proto.ListAttachmentResponse{
Attachments: lo.Map(out, func(v models.Attachment, _ int) []byte {
return nex.EncodeMap(v)
}),
}, nil
}
func (v *Server) UpdateVisibility(ctx context.Context, request *proto.UpdateVisibilityRequest) (*proto.UpdateVisibilityResponse, error) {
log.Debug().Any("request", request).Msg("Update attachment visibility via grpc...")
tx := database.C
if len(request.Id) == 0 && len(request.Rid) == 0 {
return nil, status.Error(codes.InvalidArgument, "you must provide at least one id or random id")
}
if len(request.Id) > 0 {
tx = tx.Where("id IN ?", request.Id)
}
if len(request.Rid) > 0 {
tx = tx.Where("rid IN ?", request.Rid)
}
if request.UserId != nil {
tx = tx.Where("account_id = ?", request.UserId)
}
var rowsAffected int64
if err := tx.Updates(&models.Attachment{IsIndexable: request.IsIndexable}).Error; err != nil {
return nil, status.Error(codes.Internal, err.Error())
} else {
rowsAffected = tx.RowsAffected
}
return &proto.UpdateVisibilityResponse{
Count: int32(rowsAffected),
}, nil
}
func (v *Server) UpdateUsage(ctx context.Context, request *proto.UpdateUsageRequest) (*proto.UpdateUsageResponse, error) {
tx := database.C
if len(request.Id) == 0 && len(request.Rid) == 0 {
return nil, status.Error(codes.InvalidArgument, "you must provide at least one id or random id")
}
if len(request.Id) > 0 {
tx = tx.Where("id IN ?", request.Id)
}
if len(request.Rid) > 0 {
tx = tx.Where("rid IN ?", request.Rid)
}
if rows, err := services.CountAttachmentUsage(tx, int(request.GetDelta())); err != nil {
return nil, status.Error(codes.Internal, err.Error())
} else {
return &proto.UpdateUsageResponse{
Count: int32(rows),
}, nil
}
}
func (v *Server) DeleteAttachment(ctx context.Context, request *proto.DeleteAttachmentRequest) (*proto.DeleteAttachmentResponse, error) {
tx := database.C
if len(request.Id) == 0 && len(request.Rid) == 0 {
return nil, status.Error(codes.InvalidArgument, "you must provide at least one id or random id")
}
if len(request.Id) > 0 {
tx = tx.Where("id IN ?", request.Id)
}
if len(request.Rid) > 0 {
tx = tx.Where("rid IN ?", request.Rid)
}
if request.UserId != nil {
tx = tx.Where("account_id = ?", request.UserId)
}
var rowsAffected int64
if err := tx.Delete(&models.Attachment{}).Error; err != nil {
return nil, status.Error(codes.Internal, err.Error())
} else {
rowsAffected = tx.RowsAffected
}
return &proto.DeleteAttachmentResponse{
Count: int32(rowsAffected),
}, nil
}

View File

@@ -1,9 +1,10 @@
package grpc
import (
"git.solsynth.dev/hypernet/paperclip/pkg/proto"
"net"
"git.solsynth.dev/hydrogen/dealer/pkg/proto"
nproto "git.solsynth.dev/hypernet/nexus/pkg/proto"
"github.com/spf13/viper"
"google.golang.org/grpc"
health "google.golang.org/grpc/health/grpc_health_v1"
@@ -11,25 +12,32 @@ import (
)
type Server struct {
proto.UnimplementedServiceDirectoryServer
nproto.UnimplementedDirectoryServiceServer
proto.UnimplementedAttachmentServiceServer
health.UnimplementedHealthServer
srv *grpc.Server
}
var S *grpc.Server
func NewGrpc() *Server {
server := &Server{
srv: grpc.NewServer(),
}
func NewGRPC() {
S = grpc.NewServer()
nproto.RegisterDirectoryServiceServer(server.srv, server)
proto.RegisterAttachmentServiceServer(server.srv, server)
health.RegisterHealthServer(server.srv, server)
health.RegisterHealthServer(S, &Server{})
proto.RegisterServiceDirectoryServer(S, &Server{})
reflection.Register(server.srv)
reflection.Register(S)
return server
}
func ListenGRPC() error {
func (v *Server) Listen() error {
listener, err := net.Listen("tcp", viper.GetString("grpc_bind"))
if err != nil {
return err
}
return S.Serve(listener)
return v.srv.Serve(listener)
}

View File

@@ -2,28 +2,40 @@ package grpc
import (
"context"
"strconv"
"git.solsynth.dev/hydrogen/dealer/pkg/proto"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
"git.solsynth.dev/hypernet/nexus/pkg/nex"
jsoniter "github.com/json-iterator/go"
"git.solsynth.dev/hypernet/nexus/pkg/proto"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
)
func (v *Server) BroadcastDeletion(ctx context.Context, request *proto.DeletionRequest) (*proto.DeletionResponse, error) {
switch request.GetResourceType() {
case "account":
numericId, err := strconv.Atoi(request.GetResourceId())
if err != nil {
func (v *Server) BroadcastEvent(ctx context.Context, in *proto.EventInfo) (*proto.EventResponse, error) {
switch in.GetEvent() {
case "deletion":
data := nex.DecodeMap(in.GetData())
resType, ok := data["type"].(string)
if !ok {
break
}
for _, model := range database.AutoMaintainRange {
switch model.(type) {
default:
database.C.Delete(model, "account_id = ?", numericId)
switch resType {
case "account":
var data struct {
ID int `json:"id"`
}
if err := jsoniter.Unmarshal(in.GetData(), &data); err != nil {
break
}
tx := database.C.Begin()
for _, model := range database.AutoMaintainRange {
switch model.(type) {
default:
tx.Delete(model, "account_id = ?", data.ID)
}
}
tx.Commit()
}
database.C.Delete(&models.Account{}, "id = ?", numericId)
}
return &proto.DeletionResponse{}, nil
return &proto.EventResponse{}, nil
}

View File

@@ -1,10 +0,0 @@
package models
import "git.solsynth.dev/hydrogen/dealer/pkg/hyper"
type Account struct {
hyper.BaseUser
Attachments []Attachment `json:"attachments"`
Pools []AttachmentPool `json:"pools"`
}

View File

@@ -1,51 +0,0 @@
package models
import (
"time"
"git.solsynth.dev/hydrogen/dealer/pkg/hyper"
"gorm.io/datatypes"
)
type AttachmentDst = int8
const (
AttachmentDstTemporary = AttachmentDst(iota)
AttachmentDstPermanent
)
type Attachment struct {
hyper.BaseModel
// Random ID is for accessing (appear in URL)
Rid string `json:"rid" gorm:"uniqueIndex"`
// Unique ID is for storing (appear in local file name or object name)
Uuid string `json:"uuid"`
Size int64 `json:"size"`
Name string `json:"name"`
Alternative string `json:"alt"`
MimeType string `json:"mimetype"`
HashCode string `json:"hash"`
Destination AttachmentDst `json:"destination"`
RefCount int `json:"ref_count"`
FileChunks datatypes.JSONMap `json:"file_chunks"`
CleanedAt *time.Time `json:"cleaned_at"`
Metadata datatypes.JSONMap `json:"metadata"`
IsMature bool `json:"is_mature"`
IsAnalyzed bool `json:"is_analyzed"`
IsUploaded bool `json:"is_uploaded"`
IsSelfRef bool `json:"is_self_ref"`
Ref *Attachment `json:"ref"`
RefID *uint `json:"ref_id"`
Pool *AttachmentPool `json:"pool"`
PoolID *uint `json:"pool_id"`
Account Account `json:"account"`
AccountID uint `json:"account_id"`
}

View File

@@ -1,28 +0,0 @@
package models
const (
DestinationTypeLocal = "local"
DestinationTypeS3 = "s3"
)
type BaseDestination struct {
Type string `json:"type"`
}
type LocalDestination struct {
BaseDestination
Path string `json:"path"`
}
type S3Destination struct {
BaseDestination
Path string `json:"path"`
Bucket string `json:"bucket"`
Endpoint string `json:"endpoint"`
SecretID string `json:"secret_id"`
SecretKey string `json:"secret_key"`
AccessBaseURL string `json:"access_baseurl"`
EnableSSL bool `json:"enable_ssl"`
}

View File

@@ -2,71 +2,74 @@ package api
import (
"fmt"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/gap"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/server/exts"
"net/url"
"path/filepath"
"strings"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/services"
"github.com/gofiber/fiber/v2"
jsoniter "github.com/json-iterator/go"
"github.com/samber/lo"
"github.com/spf13/viper"
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/server/exts"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/services"
"github.com/gofiber/fiber/v2"
)
func getBillingStatus(c *fiber.Ctx) error {
if err := sec.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("nex_user").(*sec.UserInfo)
currentBytes, err := services.GetLastDayUploadedBytes(user.ID)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
discountFileSize := viper.GetInt64("payment.discount")
return c.JSON(fiber.Map{
"current_bytes": currentBytes,
"discount_file_size": discountFileSize,
"included_ratio": float64(currentBytes) / float64(discountFileSize),
})
}
func openAttachment(c *fiber.Ctx) error {
id := c.Params("id")
region := c.Query("region")
metadata, err := services.GetAttachmentByRID(id)
if err != nil {
return fiber.NewError(fiber.StatusNotFound)
} else if !metadata.IsUploaded {
return fiber.NewError(fiber.StatusNotFound, "file is in uploading progress, please wait until all chunk uploaded")
}
var destMap map[string]any
if metadata.Destination == models.AttachmentDstTemporary {
destMap = viper.GetStringMap("destinations.temporary")
var err error
var url, mimetype string
var filesize int64
size := lo.Ternary(c.QueryBool("preview", true), 1024, -1)
if len(region) > 0 {
url, filesize, mimetype, err = services.OpenAttachmentByRID(id, size, region)
} else {
destMap = viper.GetStringMap("destinations.permanent")
url, filesize, mimetype, err = services.OpenAttachmentByRID(id, size)
}
var dest models.BaseDestination
rawDest, _ := jsoniter.Marshal(destMap)
_ = jsoniter.Unmarshal(rawDest, &dest)
switch dest.Type {
case models.DestinationTypeLocal:
var destConfigured models.LocalDestination
_ = jsoniter.Unmarshal(rawDest, &destConfigured)
if len(metadata.MimeType) > 0 {
c.Set(fiber.HeaderContentType, metadata.MimeType)
}
return c.SendFile(filepath.Join(destConfigured.Path, metadata.Uuid), false)
case models.DestinationTypeS3:
var destConfigured models.S3Destination
_ = jsoniter.Unmarshal(rawDest, &destConfigured)
if len(destConfigured.AccessBaseURL) > 0 {
return c.Redirect(fmt.Sprintf(
"%s/%s",
destConfigured.AccessBaseURL,
url.QueryEscape(filepath.Join(destConfigured.Path, metadata.Uuid)),
), fiber.StatusMovedPermanently)
} else {
protocol := lo.Ternary(destConfigured.EnableSSL, "https", "http")
return c.Redirect(fmt.Sprintf(
"%s://%s.%s/%s",
protocol,
destConfigured.Bucket,
destConfigured.Endpoint,
url.QueryEscape(filepath.Join(destConfigured.Path, metadata.Uuid)),
), fiber.StatusMovedPermanently)
}
default:
return fmt.Errorf("invalid destination: unsupported protocol %s", dest.Type)
authenticated := false
if err := sec.EnsureAuthenticated(c); err == nil {
authenticated = true
}
if filesize > viper.GetInt64("traffic.maximum_size") && !authenticated {
return fiber.NewError(fiber.StatusForbidden, "file is too large, you need authorized to access")
}
if err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
c.Set(fiber.HeaderContentType, mimetype)
if strings.HasPrefix(url, "file://") {
fp := strings.Replace(url, "file://", "", 1)
return c.SendFile(fp)
}
return c.Redirect(url, fiber.StatusFound)
}
func getAttachmentMeta(c *fiber.Ctx) error {
@@ -82,16 +85,14 @@ func getAttachmentMeta(c *fiber.Ctx) error {
func updateAttachmentMeta(c *fiber.Ctx) error {
id, _ := c.ParamsInt("id", 0)
if err := gap.H.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
user := c.Locals("nex_user").(*sec.UserInfo)
var data struct {
Thumbnail *uint `json:"thumbnail"`
Compressed *uint `json:"compressed"`
Alternative string `json:"alt"`
Metadata map[string]any `json:"metadata"`
IsMature bool `json:"is_mature"`
IsIndexable bool `json:"is_indexable"`
}
if err := exts.BindAndValidate(c, &data); err != nil {
@@ -99,13 +100,52 @@ func updateAttachmentMeta(c *fiber.Ctx) error {
}
var attachment models.Attachment
if err := database.C.Where("id = ? AND account_id = ?", id, user.ID).First(&attachment).Error; err != nil {
if err := database.C.
Where("id = ? AND account_id = ?", id, user.ID).
Preload("Thumbnail").
Preload("Compressed").
First(&attachment).Error; err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
if data.Thumbnail != nil && attachment.ThumbnailID != data.Thumbnail {
var thumbnail models.Attachment
if err := database.C.
Where("id = ? AND account_id = ?", data.Thumbnail, user.ID).
First(&thumbnail).Error; err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable find thumbnail: %v", err))
}
if attachment.Thumbnail != nil {
services.UnsetAttachmentAsThumbnail(*attachment.Thumbnail)
}
thumbnail, err := services.SetAttachmentAsThumbnail(thumbnail)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable set thumbnail: %v", err))
}
attachment.Thumbnail = &thumbnail
attachment.ThumbnailID = &thumbnail.ID
}
if data.Compressed != nil && attachment.CompressedID != data.Compressed {
var compressed models.Attachment
if err := database.C.
Where("id = ? AND account_id = ?", data.Compressed, user.ID).
First(&compressed).Error; err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable find compressed: %v", err))
}
if attachment.Compressed != nil {
services.UnsetAttachmentAsCompressed(*attachment.Compressed)
}
compressed, err := services.SetAttachmentAsCompressed(compressed)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable set compressed: %v", err))
}
attachment.Compressed = &compressed
attachment.CompressedID = &compressed.ID
}
attachment.Alternative = data.Alternative
attachment.Metadata = data.Metadata
attachment.IsMature = data.IsMature
attachment.Usermeta = data.Metadata
attachment.IsIndexable = data.IsIndexable
if attachment, err := services.UpdateAttachment(attachment); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
@@ -114,13 +154,41 @@ func updateAttachmentMeta(c *fiber.Ctx) error {
}
}
func deleteAttachment(c *fiber.Ctx) error {
func updateAttachmentRating(c *fiber.Ctx) error {
id, _ := c.ParamsInt("id", 0)
user := c.Locals("nex_user").(*sec.UserInfo)
if err := gap.H.EnsureAuthenticated(c); err != nil {
var data struct {
ContentRating int `json:"content_rating" validate:"required,min=3,max=21"`
QualityRating int `json:"quality_rating" validate:"min=0,max=5"`
}
if err := exts.BindAndValidate(c, &data); err != nil {
return err
}
user := c.Locals("user").(models.Account)
attachment, err := services.GetAttachmentByID(uint(id))
if err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
} else if attachment.AccountID != user.ID {
if err = sec.EnsureGrantedPerm(c, "OverrideAttachmentRating", true); err != nil {
return err
}
}
attachment.ContentRating = data.ContentRating
attachment.QualityRating = data.QualityRating
if attachment, err = services.UpdateAttachment(attachment); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else {
return c.JSON(attachment)
}
}
func deleteAttachment(c *fiber.Ctx) error {
id, _ := c.ParamsInt("id", 0)
user := c.Locals("nex_user").(*sec.UserInfo)
attachment, err := services.GetAttachmentByID(uint(id))
if err != nil {

View File

@@ -0,0 +1,141 @@
package api
import (
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/server/exts"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/services"
"github.com/gofiber/fiber/v2"
)
func listBoostByUser(c *fiber.Ctx) error {
user := c.Locals("nex_user").(*sec.UserInfo)
take := c.QueryInt("take", 0)
offset := c.QueryInt("offset", 0)
if take > 100 {
take = 100
}
count, err := services.CountBoostByUser(user.ID)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
boosts, err := services.ListBoostByUser(user.ID, take, offset)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
return c.JSON(fiber.Map{
"count": count,
"data": boosts,
})
}
func listBoostByAttachment(c *fiber.Ctx) error {
attachmentId, _ := c.ParamsInt("attachmentId", 0)
if boost, err := services.ListBoostByAttachment(uint(attachmentId)); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} else {
return c.JSON(boost)
}
}
func getBoost(c *fiber.Ctx) error {
boostId, _ := c.ParamsInt("boostId", 0)
if boost, err := services.GetBoostByID(uint(boostId)); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} else {
return c.JSON(boost)
}
}
func createBoost(c *fiber.Ctx) error {
user := c.Locals("nex_user").(*sec.UserInfo)
var data struct {
Attachment uint `json:"attachment" validate:"required"`
Destination int `json:"destination" validate:"required"`
}
if err := exts.BindAndValidate(c, &data); err != nil {
return err
}
var attachment models.Attachment
if err := database.C.Where("id = ?", data.Attachment).First(&attachment).Error; err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
if boost, err := services.CreateBoost(user, attachment, data.Destination); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else {
return c.JSON(boost)
}
}
func activateBoost(c *fiber.Ctx) error {
user := c.Locals("nex_user").(*sec.UserInfo)
boostId, _ := c.ParamsInt("boostId", 0)
boost, err := services.GetBoostByID(uint(boostId))
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} else if boost.AccountID != user.ID {
return fiber.NewError(fiber.StatusNotFound, "record not created by you")
}
if err := services.ActivateBoost(boost); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else {
return c.JSON(boost)
}
}
func updateBoost(c *fiber.Ctx) error {
user := c.Locals("nex_user").(*sec.UserInfo)
boostId, _ := c.ParamsInt("boostId", 0)
var data struct {
Status int `json:"status" validate:"required"`
}
if err := exts.BindAndValidate(c, &data); err != nil {
return err
}
boost, err := services.GetBoostByID(uint(boostId))
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} else if boost.AccountID != user.ID {
return fiber.NewError(fiber.StatusNotFound, "record not created by you")
}
if boost, err := services.UpdateBoostStatus(boost, data.Status); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else {
return c.JSON(boost)
}
}
func deleteBoost(c *fiber.Ctx) error {
user := c.Locals("nex_user").(*sec.UserInfo)
boostId, _ := c.ParamsInt("boostId", 0)
boost, err := services.GetBoostByID(uint(boostId))
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} else if boost.AccountID != user.ID {
return fiber.NewError(fiber.StatusNotFound, "record not created by you")
}
if err := services.DeleteBoost(boost); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} else {
return c.SendStatus(fiber.StatusOK)
}
}

View File

@@ -0,0 +1,19 @@
package api
import (
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/services"
"github.com/gofiber/fiber/v2"
jsoniter "github.com/json-iterator/go"
)
func listDestination(c *fiber.Ctx) error {
var destinations []models.BaseDestination
for _, value := range services.DestinationsByIndex {
var parsed models.BaseDestination
_ = jsoniter.Unmarshal(value.Raw, &parsed)
parsed.ID = value.Index
destinations = append(destinations, parsed)
}
return c.JSON(destinations)
}

View File

@@ -1,40 +1,77 @@
package api
import "github.com/gofiber/fiber/v2"
import (
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
"github.com/gofiber/fiber/v2"
)
func MapAPIs(app *fiber.App, baseURL string) {
app.Get("/.well-known/destinations", getDestinations)
api := app.Group(baseURL).Name("API")
{
api.Get("/pools", listPost)
api.Get("/pools/:id", getPool)
api.Post("/pools", createPool)
api.Put("/pools/:id", updatePool)
api.Delete("/pools/:id", deletePool)
api.Get("/destinations", listDestination)
api.Get("/billing", getBillingStatus)
api.Get("/attachments", listAttachment)
api.Get("/attachments/:id/meta", getAttachmentMeta)
api.Get("/attachments/:id", openAttachment)
api.Post("/attachments", createAttachmentDirectly)
api.Put("/attachments/:id", updateAttachmentMeta)
api.Delete("/attachments/:id", deleteAttachment)
boost := api.Group("/boosts").Name("Boosts API")
{
boost.Get("/", listBoostByUser)
boost.Get("/:boostId", getBoost)
boost.Post("/", sec.ValidatorMiddleware, createBoost)
boost.Post("/:boostId/activate", sec.ValidatorMiddleware, activateBoost)
boost.Put("/:boostId", sec.ValidatorMiddleware, updateBoost)
}
api.Post("/attachments/multipart", createAttachmentMultipartPlaceholder)
api.Post("/attachments/multipart/:file/:chunk", uploadAttachmentMultipart)
pools := api.Group("/pools").Name("Pools API")
{
pools.Get("/", listPool)
pools.Get("/:id", getPool)
pools.Post("/", sec.ValidatorMiddleware, createPool)
pools.Put("/:id", sec.ValidatorMiddleware, updatePool)
pools.Delete("/:id", sec.ValidatorMiddleware, deletePool)
}
api.Get("/stickers/lookup", lookupStickerBatch)
api.Get("/stickers/lookup/:alias", lookupSticker)
api.Get("/stickers/packs", listStickerPacks)
api.Get("/stickers/packs/:packId", getStickerPack)
api.Post("/stickers/packs", createStickerPack)
api.Put("/stickers/packs/:packId", updateStickerPack)
api.Delete("/stickers/packs/:packId", deleteStickerPack)
attachments := api.Group("/attachments").Name("Attachments API")
{
attachments.Get("/:attachmentId/boosts", listBoostByAttachment)
api.Get("/stickers", listStickers)
api.Get("/stickers/:stickerId", getSticker)
api.Post("/stickers", createSticker)
api.Put("/stickers/:stickerId", updateSticker)
api.Delete("/stickers/:stickerId", deleteSticker)
attachments.Get("/", listAttachment)
attachments.Get("/:id/meta", getAttachmentMeta)
attachments.Get("/:id", openAttachment)
attachments.Post("/", sec.ValidatorMiddleware, createAttachmentDirectly)
attachments.Post("/referenced", sec.ValidatorMiddleware, createAttachmentWithURL)
attachments.Put("/:id", sec.ValidatorMiddleware, updateAttachmentMeta)
attachments.Put("/:id/rating", sec.ValidatorMiddleware, updateAttachmentRating)
attachments.Delete("/:id", sec.ValidatorMiddleware, deleteAttachment)
}
fragments := api.Group("/fragments").Name("Fragments API")
{
fragments.Post("/", sec.ValidatorMiddleware, createAttachmentFragment)
fragments.Post("/:file/:chunk", sec.ValidatorMiddleware, uploadFragmentChunk)
}
stickers := api.Group("/stickers").Name("Stickers API")
{
packs := stickers.Group("/packs").Name("Sticker Packs API")
{
packs.Get("/", listStickerPacks)
packs.Get("/own", listOwnedStickerPacks)
packs.Get("/:packId", getStickerPack)
packs.Post("/", sec.ValidatorMiddleware, createStickerPack)
packs.Put("/:packId", sec.ValidatorMiddleware, updateStickerPack)
packs.Delete("/:packId", sec.ValidatorMiddleware, deleteStickerPack)
packs.Post("/:packId/own", addStickerPack)
packs.Delete("/:packId/own", removeStickerPack)
}
stickers.Get("/lookup", lookupStickerBatch)
stickers.Get("/lookup/:alias", getStickerByAlias)
stickers.Get("/lookup/:alias/open", openStickerByAlias)
stickers.Get("/", listStickers)
stickers.Get("/:stickerId", getSticker)
stickers.Post("/", sec.ValidatorMiddleware, createSticker)
stickers.Put("/:stickerId", sec.ValidatorMiddleware, updateSticker)
stickers.Delete("/:stickerId", sec.ValidatorMiddleware, deleteSticker)
}
}
}

View File

@@ -2,13 +2,16 @@ package api
import (
"fmt"
"github.com/spf13/viper"
"gorm.io/datatypes"
"strings"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/services"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/gap"
"git.solsynth.dev/hypernet/passport/pkg/authkit"
"github.com/spf13/viper"
"gorm.io/datatypes"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/services"
"github.com/gofiber/fiber/v2"
)
@@ -24,7 +27,7 @@ func listAttachment(c *fiber.Ctx) error {
needQuery := true
var result = make([]models.Attachment, take)
result := make([]models.Attachment, take)
var idxList []string
if len(c.Query("id")) > 0 {
@@ -41,6 +44,8 @@ func listAttachment(c *fiber.Ctx) error {
tx = tx.Where("rid IN ?", pendingQueryId)
needQuery = len(pendingQueryId) > 0
} else {
tx = tx.Where("is_indexable IS NULL OR is_indexable = ?", true)
// Do sort this when doesn't filter by the id
// Because the sort will mess up the result
tx = tx.Order("created_at DESC")
@@ -53,12 +58,9 @@ func listAttachment(c *fiber.Ctx) error {
}
if len(c.Query("author")) > 0 {
var author models.Account
if err := database.C.Where("name = ?", c.Query("author")).First(&author).Error; err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else {
prefix := viper.GetString("database.prefix")
tx = tx.Where(fmt.Sprintf("%sattachments.account_id = ?", prefix), author.ID)
author, err := authkit.GetUserByName(gap.Nx, c.Query("author"))
if err == nil {
tx = tx.Where("attachments.account_id = ?", author.ID)
}
}
@@ -82,7 +84,13 @@ func listAttachment(c *fiber.Ctx) error {
if needQuery {
var out []models.Attachment
if err := tx.Offset(offset).Limit(take).Preload("Account").Find(&out).Error; err != nil {
if err := tx.
Offset(offset).Limit(take).
Preload("Pool").
Preload("Thumbnail").
Preload("Compressed").
Preload("Boosts").
Find(&out).Error; err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
@@ -103,8 +111,13 @@ func listAttachment(c *fiber.Ctx) error {
services.CacheAttachment(item)
}
out, err := services.CompleteAttachmentMeta(result...)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
return c.JSON(fiber.Map{
"count": count,
"data": result,
"data": out,
})
}

View File

@@ -1,15 +1,15 @@
package api
import (
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/gap"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/server/exts"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/services"
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/server/exts"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/services"
"github.com/gofiber/fiber/v2"
"gorm.io/datatypes"
)
func listPost(c *fiber.Ctx) error {
func listPool(c *fiber.Ctx) error {
pools, err := services.ListAttachmentPool()
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
@@ -27,10 +27,7 @@ func getPool(c *fiber.Ctx) error {
}
func createPool(c *fiber.Ctx) error {
if err := gap.H.EnsureGrantedPerm(c, "CreateAttachmentPools", true); err != nil {
return err
}
user := c.Locals("user").(models.Account)
user := c.Locals("nex_user").(*sec.UserInfo)
var data struct {
Alias string `json:"alias" validate:"required"`
@@ -59,10 +56,7 @@ func createPool(c *fiber.Ctx) error {
}
func updatePool(c *fiber.Ctx) error {
if err := gap.H.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
user := c.Locals("nex_user").(*sec.UserInfo)
var data struct {
Alias string `json:"alias" validate:"required"`
@@ -94,10 +88,7 @@ func updatePool(c *fiber.Ctx) error {
}
func deletePool(c *fiber.Ctx) error {
if err := gap.H.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
user := c.Locals("nex_user").(*sec.UserInfo)
id, _ := c.ParamsInt("id")
pool, err := services.GetAttachmentPoolWithUser(uint(id), user.ID)

View File

@@ -1,12 +1,15 @@
package api
import (
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/gap"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/server/exts"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/services"
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/gap"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/server/exts"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/services"
"git.solsynth.dev/hypernet/passport/pkg/authkit"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
)
func listStickerPacks(c *fiber.Ctx) error {
@@ -20,10 +23,8 @@ func listStickerPacks(c *fiber.Ctx) error {
tx := database.C
if len(c.Query("author")) > 0 {
var author models.Account
if err := database.C.Where("name = ?", c.Query("author")).First(&author).Error; err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else {
author, err := authkit.GetUserByName(gap.Nx, c.Query("author"))
if err == nil {
tx = tx.Where("account_id = ?", author.ID)
}
}
@@ -44,6 +45,29 @@ func listStickerPacks(c *fiber.Ctx) error {
})
}
func listOwnedStickerPacks(c *fiber.Ctx) error {
if err := sec.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("nex_user").(*sec.UserInfo)
var ownerships []models.StickerPackOwnership
if err := database.C.Where("account_id = ?", user.ID).Find(&ownerships).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
idSet := lo.Map(ownerships, func(o models.StickerPackOwnership, _ int) uint {
return o.PackID
})
var packs []models.StickerPack
if err := database.C.Where("id IN ?", idSet).Find(&packs).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
return c.JSON(packs)
}
func getStickerPack(c *fiber.Ctx) error {
id, _ := c.ParamsInt("packId", 0)
pack, err := services.GetStickerPack(uint(id))
@@ -51,14 +75,20 @@ func getStickerPack(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
var stickers []models.Sticker
if err := database.C.Where("pack_id = ?", pack.ID).
Preload("Attachment").
Find(&stickers).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} else {
pack.Stickers = stickers
}
return c.JSON(pack)
}
func createStickerPack(c *fiber.Ctx) error {
if err := gap.H.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
user := c.Locals("nex_user").(*sec.UserInfo)
var data struct {
Prefix string `json:"prefix" validate:"required,alphanum,min=2,max=12"`
@@ -79,10 +109,7 @@ func createStickerPack(c *fiber.Ctx) error {
}
func updateStickerPack(c *fiber.Ctx) error {
if err := gap.H.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
user := c.Locals("nex_user").(*sec.UserInfo)
var data struct {
Prefix string `json:"prefix" validate:"required,alphanum,min=2,max=12"`
@@ -112,10 +139,7 @@ func updateStickerPack(c *fiber.Ctx) error {
}
func deleteStickerPack(c *fiber.Ctx) error {
if err := gap.H.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
user := c.Locals("nex_user").(*sec.UserInfo)
id, _ := c.ParamsInt("packId", 0)
pack, err := services.GetStickerPackWithUser(uint(id), user.ID)
@@ -129,3 +153,43 @@ func deleteStickerPack(c *fiber.Ctx) error {
return c.JSON(pack)
}
func addStickerPack(c *fiber.Ctx) error {
if err := sec.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("nex_user").(*sec.UserInfo)
packId, _ := c.ParamsInt("packId", 0)
var pack models.StickerPack
if err := database.C.Where("id = ?", packId).First(&pack).Error; err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
ownership, err := services.AddStickerPack(user.ID, pack)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
return c.JSON(ownership)
}
func removeStickerPack(c *fiber.Ctx) error {
if err := sec.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("nex_user").(*sec.UserInfo)
packId, _ := c.ParamsInt("packId", 0)
var pack models.StickerPack
if err := database.C.Where("id = ?", packId).First(&pack).Error; err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
ownership, err := services.RemoveStickerPack(user.ID, pack)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
return c.JSON(ownership)
}

View File

@@ -4,11 +4,13 @@ import (
"fmt"
"strings"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/gap"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/server/exts"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/services"
"github.com/samber/lo"
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/server/exts"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/services"
"github.com/gofiber/fiber/v2"
)
@@ -21,7 +23,7 @@ func lookupStickerBatch(c *fiber.Ctx) error {
}
}
func lookupSticker(c *fiber.Ctx) error {
func getStickerByAlias(c *fiber.Ctx) error {
alias := c.Params("alias")
if sticker, err := services.GetStickerWithAlias(alias); err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
@@ -30,44 +32,59 @@ func lookupSticker(c *fiber.Ctx) error {
}
}
func openStickerByAlias(c *fiber.Ctx) error {
alias := c.Params("alias")
region := c.Query("region")
sticker, err := services.GetStickerWithAlias(alias)
if err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
var url, mimetype string
if len(region) > 0 {
url, _, mimetype, err = services.OpenAttachmentByRID(sticker.Attachment.Rid, 256, region)
} else {
url, _, mimetype, err = services.OpenAttachmentByRID(sticker.Attachment.Rid, 288)
}
if err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error())
}
c.Set(fiber.HeaderContentType, mimetype)
if strings.HasPrefix(url, "file://") {
fp := strings.Replace(url, "file://", "", 1)
return c.SendFile(fp)
}
return c.Redirect(url, fiber.StatusFound)
}
func listStickers(c *fiber.Ctx) error {
take := c.QueryInt("take", 0)
offset := c.QueryInt("offset", 0)
if take > 100 {
take = 100
if err := sec.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("nex_user").(*sec.UserInfo)
tx := database.C
if len(c.Query("author")) > 0 {
var author models.Account
if err := database.C.Where("name = ?", c.Query("author")).First(&author).Error; err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else {
tx = tx.Where("account_id = ?", author.ID)
}
}
if val := c.QueryInt("pack", 0); val > 0 {
tx = tx.Where("pack_id = ?", val)
}
var count int64
countTx := tx
if err := countTx.Model(&models.Sticker{}).Count(&count).Error; err != nil {
var ownerships []models.StickerPackOwnership
if err := database.C.Where("account_id = ?", user.ID).Find(&ownerships).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
tx := database.C.Where("pack_id IN ?", lo.Map(ownerships, func(o models.StickerPackOwnership, _ int) uint {
return o.PackID
}))
var stickers []models.Sticker
if err := tx.Limit(take).Offset(offset).Preload("Attachment").Find(&stickers).Error; err != nil {
if err := tx.
Preload("Attachment").Preload("Pack").
Find(&stickers).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
return c.JSON(fiber.Map{
"count": count,
"data": stickers,
})
return c.JSON(stickers)
}
func getSticker(c *fiber.Ctx) error {
@@ -80,10 +97,7 @@ func getSticker(c *fiber.Ctx) error {
}
func createSticker(c *fiber.Ctx) error {
if err := gap.H.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
user := c.Locals("nex_user").(*sec.UserInfo)
var data struct {
Alias string `json:"alias" validate:"required,alphanum,min=2,max=12"`
@@ -128,10 +142,7 @@ func createSticker(c *fiber.Ctx) error {
}
func updateSticker(c *fiber.Ctx) error {
if err := gap.H.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
user := c.Locals("nex_user").(*sec.UserInfo)
var data struct {
Alias string `json:"alias" validate:"required,alphanum,min=2,max=12"`
@@ -179,10 +190,7 @@ func updateSticker(c *fiber.Ctx) error {
}
func deleteSticker(c *fiber.Ctx) error {
if err := gap.H.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
user := c.Locals("nex_user").(*sec.UserInfo)
id, _ := c.ParamsInt("stickerId", 0)
sticker, err := services.GetStickerWithUser(uint(id), user.ID)

View File

@@ -2,20 +2,19 @@ package api
import (
"fmt"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/gap"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/services"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/server/exts"
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/services"
"github.com/gofiber/fiber/v2"
jsoniter "github.com/json-iterator/go"
"github.com/spf13/viper"
)
func createAttachmentDirectly(c *fiber.Ctx) error {
if err := gap.H.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
user := c.Locals("nex_user").(*sec.UserInfo)
poolAlias := c.FormValue("pool")
@@ -34,8 +33,8 @@ func createAttachmentDirectly(c *fiber.Ctx) error {
return err
}
if err = gap.H.EnsureGrantedPerm(c, "CreateAttachments", file.Size); err != nil {
return err
if !user.HasPermNode("CreateAttachments", true) {
return fiber.NewError(fiber.StatusForbidden, "you are not permitted to create attachments")
} else if pool.Config.Data().MaxFileSize != nil && file.Size > *pool.Config.Data().MaxFileSize {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("attachment pool %s doesn't allow file larger than %d", pool.Alias, *pool.Config.Data().MaxFileSize))
}
@@ -48,10 +47,8 @@ func createAttachmentDirectly(c *fiber.Ctx) error {
metadata, err := services.NewAttachmentMetadata(tx, user, file, models.Attachment{
Alternative: c.FormValue("alt"),
MimeType: c.FormValue("mimetype"),
Metadata: usermeta,
IsMature: len(c.FormValue("mature")) > 0,
Usermeta: usermeta,
IsAnalyzed: false,
IsUploaded: true,
Destination: models.AttachmentDstTemporary,
Pool: &pool,
PoolID: &pool.ID,
@@ -66,11 +63,69 @@ func createAttachmentDirectly(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
// If pool has no belongs to, it means it is shared pool, apply shared attachment discount
withDiscount := pool.AccountID == nil
if err := services.PlaceOrder(user.ID, file.Size, withDiscount); err != nil {
tx.Rollback()
return fiber.NewError(fiber.StatusPaymentRequired, err.Error())
}
tx.Commit()
metadata.Account = user
metadata.Pool = &pool
services.PublishAnalyzeTask(metadata)
if c.QueryBool("analyzeNow", false) {
services.AnalyzeAttachment(metadata)
} else {
services.PublishAnalyzeTask(metadata)
}
return c.JSON(metadata)
}
func createAttachmentWithURL(c *fiber.Ctx) error {
user := c.Locals("nex_user").(*sec.UserInfo)
poolAlias := c.FormValue("pool")
aliasingMap := viper.GetStringMapString("pools.aliases")
if val, ok := aliasingMap[poolAlias]; ok {
poolAlias = val
}
var data struct {
URL string `json:"url"`
Metadata map[string]any `json:"metadata"`
Mimetype string `json:"mimetype"`
Name string `json:"filename"`
Alternative string `json:"alt"`
}
if err := exts.BindAndValidate(c, &data); err != nil {
return err
}
if !user.HasPermNode("CreateReferencedAttachments", true) {
return fiber.NewError(fiber.StatusForbidden, "you are not permitted to create attachments with URL")
}
pool, err := services.GetAttachmentPoolByAlias(poolAlias)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable to get attachment pool info: %v", err))
}
attachment := models.Attachment{
Name: data.Name,
Alternative: data.Alternative,
MimeType: c.FormValue("mimetype"),
Usermeta: data.Metadata,
IsAnalyzed: true,
Destination: models.AttachmentDstExternal,
Pool: &pool,
PoolID: &pool.ID,
}
if attachment, err = services.NewRefURLAttachment(database.C, user, attachment); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
return c.JSON(attachment)
}

View File

@@ -3,20 +3,19 @@ package api
import (
"encoding/json"
"fmt"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/gap"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/server/exts"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/services"
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/fs"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/server/exts"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/services"
"github.com/gofiber/fiber/v2"
"github.com/spf13/viper"
)
func createAttachmentMultipartPlaceholder(c *fiber.Ctx) error {
if err := gap.H.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
func createAttachmentFragment(c *fiber.Ctx) error {
user := c.Locals("nex_user").(*sec.UserInfo)
var data struct {
Pool string `json:"pool" validate:"required"`
@@ -24,8 +23,8 @@ func createAttachmentMultipartPlaceholder(c *fiber.Ctx) error {
FileName string `json:"name" validate:"required"`
Alternative string `json:"alt"`
MimeType string `json:"mimetype"`
Fingerprint *string `json:"fingerprint"`
Metadata map[string]any `json:"metadata"`
IsMature bool `json:"is_mature"`
}
if err := exts.BindAndValidate(c, &data); err != nil {
@@ -42,28 +41,39 @@ func createAttachmentMultipartPlaceholder(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable to get attachment pool info: %v", err))
}
if err = gap.H.EnsureGrantedPerm(c, "CreateAttachments", data.Size); err != nil {
return err
if !user.HasPermNode("CreateAttachments", true) {
return fiber.NewError(fiber.StatusForbidden, "you are not permitted to create attachments")
} else if pool.Config.Data().MaxFileSize != nil && *pool.Config.Data().MaxFileSize > data.Size {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("attachment pool %s doesn't allow file larger than %d", pool.Alias, *pool.Config.Data().MaxFileSize))
}
metadata, err := services.NewAttachmentPlaceholder(database.C, user, models.Attachment{
tx := database.C.Begin()
metadata, err := services.NewAttachmentFragment(tx, user, models.AttachmentFragment{
Name: data.FileName,
Size: data.Size,
Alternative: data.Alternative,
MimeType: data.MimeType,
Metadata: data.Metadata,
IsMature: data.IsMature,
IsAnalyzed: false,
Destination: models.AttachmentDstTemporary,
Usermeta: data.Metadata,
Fingerprint: data.Fingerprint,
Pool: &pool,
PoolID: &pool.ID,
})
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else {
metadata.FileChunksMissing = services.FindFragmentMissingChunks(metadata)
}
// If pool has no belongs to, it means it is shared pool, apply shared attachment discount
withDiscount := pool.AccountID == nil
if err := services.PlaceOrder(user.ID, data.Size, withDiscount); err != nil {
tx.Rollback()
return fiber.NewError(fiber.StatusPaymentRequired, err.Error())
}
tx.Commit()
return c.JSON(fiber.Map{
"chunk_size": viper.GetInt64("performance.file_chunk_size"),
"chunk_count": len(metadata.FileChunks),
@@ -71,23 +81,20 @@ func createAttachmentMultipartPlaceholder(c *fiber.Ctx) error {
})
}
func uploadAttachmentMultipart(c *fiber.Ctx) error {
if err := gap.H.EnsureAuthenticated(c); err != nil {
return err
}
user := c.Locals("user").(models.Account)
func uploadFragmentChunk(c *fiber.Ctx) error {
user := c.Locals("nex_user").(*sec.UserInfo)
rid := c.Params("file")
cid := c.Params("chunk")
file, err := c.FormFile("file")
if err != nil {
return err
} else if file.Size > viper.GetInt64("performance.file_chunk_size") {
fileData := c.Body()
if len(fileData) == 0 {
return fiber.NewError(fiber.StatusBadRequest, "no file data")
} else if len(fileData) > viper.GetInt("performance.file_chunk_size") {
return fiber.NewError(fiber.StatusBadRequest, "file is too large for one chunk")
}
meta, err := services.GetAttachmentByRID(rid)
meta, err := services.GetFragmentByRID(rid)
if err != nil {
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("attachment was not found: %v", err))
} else if user.ID != meta.AccountID {
@@ -96,18 +103,18 @@ func uploadAttachmentMultipart(c *fiber.Ctx) error {
if _, ok := meta.FileChunks[cid]; !ok {
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("chunk %s was not found", cid))
} else if services.CheckChunkExistsInTemporary(meta, cid) {
} else if services.CheckFragmentChunkExists(meta, cid) {
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("chunk %s was uploaded", cid))
}
if err := services.UploadChunkToTemporary(c, cid, file, meta); err != nil {
if err := services.UploadFragmentChunkBytes(c, cid, fileData, meta); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
chunkArrange := make([]string, len(meta.FileChunks))
isAllUploaded := true
for cid, idx := range meta.FileChunks {
if !services.CheckChunkExistsInTemporary(meta, cid) {
if !services.CheckFragmentChunkExists(meta, cid) {
isAllUploaded = false
break
} else if val, ok := idx.(json.Number); ok {
@@ -117,12 +124,31 @@ func uploadAttachmentMultipart(c *fiber.Ctx) error {
}
if !isAllUploaded {
return c.JSON(meta)
return c.JSON(fiber.Map{
"is_finished": false,
"fragment": meta,
})
}
if meta, err = services.MergeFileChunks(meta, chunkArrange); err != nil {
// Merge & post-upload
attachment, err := fs.MergeFileChunks(meta, chunkArrange)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} else {
return c.JSON(meta)
}
// Post-upload tasks
if err := database.C.Save(&attachment).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
if c.QueryBool("analyzeNow", false) {
services.AnalyzeAttachment(attachment)
} else {
services.PublishAnalyzeTask(attachment)
}
return c.JSON(fiber.Map{
"is_finished": true,
"attachment": attachment,
})
}

View File

@@ -1,18 +0,0 @@
package api
import (
"github.com/gofiber/fiber/v2"
"github.com/spf13/viper"
)
func getDestinations(c *fiber.Ctx) error {
var data []string
for key := range viper.GetStringMap("destinations") {
data = append(data, key)
}
return c.JSON(fiber.Map{
"data": data,
"preferred": viper.GetString("preferred_destination"),
})
}

View File

@@ -3,11 +3,9 @@ package server
import (
"strings"
"git.solsynth.dev/hydrogen/dealer/pkg/hyper"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/gap"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/server/api"
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/server/api"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
@@ -18,18 +16,23 @@ import (
"github.com/spf13/viper"
)
var app *fiber.App
var IReader *sec.InternalTokenReader
func NewServer() {
app = fiber.New(fiber.Config{
type App struct {
app *fiber.App
}
func NewServer() *App {
app := fiber.New(fiber.Config{
DisableStartupMessage: true,
EnableIPValidation: true,
ServerHeader: "Hydrogen.Paperclip",
AppName: "Hydrogen.Paperclip",
ServerHeader: "Hypernet.Paperclip",
AppName: "Hypernet.Paperclip",
ProxyHeader: fiber.HeaderXForwardedFor,
JSONEncoder: jsoniter.ConfigCompatibleWithStandardLibrary.Marshal,
JSONDecoder: jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal,
BodyLimit: 512 * 1024 * 1024 * 1024, // 512 TiB
ReadBufferSize: 5 * 1024 * 1024, // 5MB for large JWT
EnablePrintRoutes: viper.GetBool("debug.print_routes"),
})
@@ -55,23 +58,15 @@ func NewServer() {
Output: log.Logger,
}))
tablePrefix := viper.GetString("database.prefix")
app.Use(gap.H.AuthMiddleware)
app.Use(hyper.LinkAccountMiddleware(
database.C,
tablePrefix+"accounts",
func(u hyper.BaseUser) models.Account {
return models.Account{
BaseUser: u,
}
},
))
app.Use(sec.ContextMiddleware(IReader))
api.MapAPIs(app, "/api")
return &App{app}
}
func Listen() {
if err := app.Listen(viper.GetString("bind")); err != nil {
func (v *App) Listen() {
if err := v.app.Listen(viper.GetString("bind")); err != nil {
log.Fatal().Err(err).Msg("An error occurred when starting server...")
}
}

View File

@@ -5,8 +5,6 @@ import (
"crypto/sha256"
"encoding/hex"
"fmt"
"github.com/barasher/go-exiftool"
"github.com/samber/lo"
"image"
"io"
"os"
@@ -15,13 +13,19 @@ import (
"strings"
"time"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
"github.com/barasher/go-exiftool"
"github.com/samber/lo"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/fs"
jsoniter "github.com/json-iterator/go"
"github.com/k0kubun/go-ansi"
"github.com/kettek/apng"
"github.com/rs/zerolog/log"
"github.com/schollz/progressbar/v3"
"github.com/spf13/viper"
"golang.org/x/image/webp"
"gopkg.in/vansante/go-ffprobe.v2"
_ "image/gif"
@@ -97,10 +101,39 @@ func ScanUnanalyzedFileFromDatabase() {
}()
}
func parseExifOrientation(src string) int {
switch src {
case "Horizontal":
return 1
case "Mirror horizontal":
return 2
case "Rotate 180":
return 3
case "Mirror vertical":
return 4
case "Mirror horizontal and rotate 270 CW":
return 5
case "Rotate 90 CW":
return 6
case "Mirror horizontal and rotate 90 CW":
return 7
case "Rotate 270 CW":
return 8
default:
return 0
}
}
func calculateAspectRatio(width, height int, orientation int) float64 {
switch orientation {
case 5, 6, 7, 8:
width, height = height, width
}
return float64(width) / float64(height)
}
func AnalyzeAttachment(file models.Attachment) error {
if !file.IsUploaded {
return fmt.Errorf("file isn't finish multipart upload")
} else if file.Destination != models.AttachmentDstTemporary {
if file.Destination != models.AttachmentDstTemporary {
return fmt.Errorf("attachment isn't in temporary storage, unable to analyze")
}
@@ -116,7 +149,7 @@ func AnalyzeAttachment(file models.Attachment) error {
// Do analyze jobs
if !file.IsAnalyzed || len(file.HashCode) == 0 {
destMap := viper.GetStringMap("destinations.temporary")
destMap := viper.GetStringMap("destinations.0")
var dest models.LocalDestination
rawDest, _ := jsoniter.Marshal(destMap)
@@ -133,7 +166,7 @@ func AnalyzeAttachment(file models.Attachment) error {
"Model", "ShutterSpeed", "ISO", "Megapixels", "Aperture",
"ColorSpace", "ColorTemperature", "ColorTone", "Contrast",
"ExposureTime", "FNumber", "FocalLength", "Flash", "HDREffect",
"LensModel",
"LensModel", "Orientation",
}
switch strings.SplitN(file.MimeType, "/", 2)[0] {
@@ -144,7 +177,15 @@ func AnalyzeAttachment(file models.Attachment) error {
return fmt.Errorf("unable to open file: %v", err)
}
defer reader.Close()
im, _, err := image.Decode(reader)
var im image.Image
switch file.MimeType {
case "image/webp":
im, err = webp.Decode(reader)
case "image/apng":
im, err = apng.Decode(reader)
default:
im, _, err = image.Decode(reader)
}
if err != nil {
return fmt.Errorf("unable to decode file as an image: %v", err)
}
@@ -164,7 +205,11 @@ func AnalyzeAttachment(file models.Attachment) error {
defer et.Close()
exif := et.ExtractMetadata(dst)
for _, data := range exif {
for k, _ := range data.Fields {
for k := range data.Fields {
if k == "Orientation" {
ori := parseExifOrientation(data.Fields[k].(string))
file.Metadata["ratio"] = calculateAspectRatio(width, height, ori)
}
if strings.HasPrefix(k, "GPS") {
data.Clear(k)
} else if lo.Contains(exifWhitelist, k) {
@@ -205,7 +250,11 @@ func AnalyzeAttachment(file models.Attachment) error {
defer et.Close()
exif := et.ExtractMetadata(dst)
for _, data := range exif {
for k, _ := range data.Fields {
for k := range data.Fields {
if k == "Orientation" {
ori := parseExifOrientation(data.Fields[k].(string))
file.Metadata["ratio"] = calculateAspectRatio(stream.Width, stream.Height, ori)
}
if strings.HasPrefix(k, "GPS") {
data.Clear(k)
} else if lo.Contains(exifWhitelist, k) {
@@ -220,46 +269,53 @@ func AnalyzeAttachment(file models.Attachment) error {
tx := database.C.Begin()
file.IsAnalyzed = true
if err := tx.Model(&file).Updates(&models.Attachment{
IsAnalyzed: true,
Metadata: file.Metadata,
HashCode: file.HashCode,
}).Error; err != nil {
tx.Rollback()
return fmt.Errorf("unable to update file record: %v", err)
}
linked, err := TryLinkAttachment(tx, file, file.HashCode)
if linked && err != nil {
return fmt.Errorf("unable to link file record: %v", err)
} else if !linked {
CacheAttachment(file)
if err := tx.Save(&file).Error; err != nil {
tx.Rollback()
return fmt.Errorf("unable to save file record: %v", err)
}
}
tx.Commit()
log.Info().Dur("elapsed", time.Since(start)).Uint("id", file.ID).Msg("A file analyze task was finished, starting uploading...")
start = time.Now()
// Move temporary to permanent
if !linked {
if err := ReUploadFileToPermanent(file); err != nil {
return fmt.Errorf("unable to move file to permanet storage: %v", err)
}
go func() {
start = time.Now()
preferred := viper.GetInt("preferred_destination")
if preferred == 0 {
preferred = 1
}
if err := ReUploadFile(file, preferred); err != nil {
log.Warn().Any("file", file).Err(err).Msg("Unable to move file to permanet storage...")
} else {
// Recycle the temporary file
file.Destination = models.AttachmentDstTemporary
go fs.DeleteFile(file)
// Finish
log.Info().Dur("elapsed", time.Since(start)).Uint("id", file.ID).Msg("A file post-analyze upload task was finished.")
}
}()
} else {
log.Info().Uint("id", file.ID).Msg("File is linked to exists one, skipping uploading...")
}
// Recycle the temporary file
file.Destination = models.AttachmentDstTemporary
PublishDeleteFileTask(file)
// Finish
log.Info().Dur("elapsed", time.Since(start)).Uint("id", file.ID).Bool("linked", linked).Msg("A file post-analyze upload task was finished.")
return nil
}
func HashAttachment(file models.Attachment) (hash string, err error) {
const chunkSize = 32 * 1024
destMap := viper.GetStringMapString("destinations.temporary")
destMap := viper.GetStringMapString("destinations.0")
destPath := filepath.Join(destMap["path"], file.Uuid)
// Check if the file exists
@@ -277,7 +333,7 @@ func HashAttachment(file models.Attachment) (hash string, err error) {
hasher := sha256.New()
if chunkSize*3 <= fileInfo.Size() {
if chunkSize*3 > fileInfo.Size() {
// If the total size is smaller than three chunks, then hash the whole file
buf := make([]byte, fileInfo.Size())
if _, err := inFile.Read(buf); err != nil && err != io.EOF {
@@ -314,7 +370,7 @@ func HashAttachment(file models.Attachment) (hash string, err error) {
}
// Hash with the file metadata
hasher.Write([]byte(fmt.Sprintf("%d", file.Size)))
fmt.Fprintf(hasher, "%d", file.Size)
// Return the combined hash
hash = hex.EncodeToString(hasher.Sum(nil))

View File

@@ -1,73 +1,95 @@
package services
import (
"context"
"fmt"
"math"
"mime"
"mime/multipart"
"net/http"
"path/filepath"
"time"
"github.com/eko/gocache/lib/v4/cache"
"github.com/eko/gocache/lib/v4/marshaler"
"github.com/eko/gocache/lib/v4/store"
"github.com/spf13/viper"
"gorm.io/datatypes"
"git.solsynth.dev/hypernet/nexus/pkg/nex/cachekit"
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
"git.solsynth.dev/hypernet/passport/pkg/authkit"
"git.solsynth.dev/hydrogen/dealer/pkg/hyper"
localCache "git.solsynth.dev/hydrogen/paperclip/pkg/internal/cache"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/fs"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/gap"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
amodels "git.solsynth.dev/hypernet/passport/pkg/authkit/models"
"github.com/google/uuid"
"github.com/samber/lo"
"gorm.io/gorm"
)
func GetAttachmentCacheKey(rid string) any {
return fmt.Sprintf("attachment#%s", rid)
func KgAttachmentCache(rid string) string {
return cachekit.FKey(cachekit.DAAttachment, rid)
}
func CompleteAttachmentMeta(in ...models.Attachment) ([]models.Attachment, error) {
var usersId []uint
for _, item := range in {
usersId = append(usersId, item.AccountID)
}
usersId = lo.Uniq(usersId)
users, err := authkit.ListUser(gap.Nx, usersId)
if err != nil {
return in, fmt.Errorf("failed to list users: %v", err)
}
for idx, item := range in {
item.Account = lo.FindOrElse(users, amodels.Account{}, func(idx amodels.Account) bool {
return item.AccountID == idx.ID
})
in[idx] = item
}
return in, nil
}
func GetAttachmentByID(id uint) (models.Attachment, error) {
var attachment models.Attachment
if err := database.C.
Where(&hyper.BaseModel{ID: id}).
Preload("Pool").Preload("Account").
Where("id = ?", id).
Preload("Pool").
Preload("Thumbnail").
Preload("Compressed").
Preload("Boosts").
First(&attachment).Error; err != nil {
return attachment, err
} else {
CacheAttachment(attachment)
}
return attachment, nil
out, err := CompleteAttachmentMeta(attachment)
return out[0], err
}
func GetAttachmentByRID(rid string) (models.Attachment, error) {
cacheManager := cache.New[any](localCache.S)
marshal := marshaler.New(cacheManager)
contx := context.Background()
if val, err := marshal.Get(
contx,
GetAttachmentCacheKey(rid),
new(models.Attachment),
if val, err := cachekit.Get[models.Attachment](
gap.Ca,
KgAttachmentCache(rid),
); err == nil {
if val.(models.Attachment).Account.ID > 0 {
return val.(models.Attachment), nil
}
return val, nil
}
var attachment models.Attachment
if err := database.C.Where(models.Attachment{
Rid: rid,
}).Preload("Pool").Preload("Account").First(&attachment).Error; err != nil {
}).
Preload("Pool").
Preload("Thumbnail").
Preload("Compressed").
Preload("Boosts").
First(&attachment).Error; err != nil {
return attachment, err
} else {
CacheAttachment(attachment)
}
return attachment, nil
out, err := CompleteAttachmentMeta(attachment)
return out[0], err
}
func GetAttachmentByHash(hash string) (models.Attachment, error) {
@@ -81,37 +103,25 @@ func GetAttachmentByHash(hash string) (models.Attachment, error) {
}
func GetAttachmentCache(rid string) (models.Attachment, bool) {
cacheManager := cache.New[any](localCache.S)
marshal := marshaler.New(cacheManager)
contx := context.Background()
if val, err := marshal.Get(
contx,
GetAttachmentCacheKey(rid),
new(models.Attachment),
if val, err := cachekit.Get[models.Attachment](
gap.Ca,
KgAttachmentCache(rid),
); err == nil {
if val.(models.Attachment).Account.ID > 0 {
return val.(models.Attachment), true
}
return val, true
}
return models.Attachment{}, false
}
func CacheAttachment(item models.Attachment) {
cacheManager := cache.New[any](localCache.S)
marshal := marshaler.New(cacheManager)
contx := context.Background()
marshal.Set(
contx,
GetAttachmentCacheKey(item.Rid),
cachekit.Set[models.Attachment](
gap.Ca,
KgAttachmentCache(item.Rid),
item,
store.WithExpiration(60*time.Minute),
store.WithTags([]string{"attachment", fmt.Sprintf("user#%d", item.AccountID)}),
60*time.Minute,
)
}
func NewAttachmentMetadata(tx *gorm.DB, user models.Account, file *multipart.FileHeader, attachment models.Attachment) (models.Attachment, error) {
func NewAttachmentMetadata(tx *gorm.DB, user *sec.UserInfo, file *multipart.FileHeader, attachment models.Attachment) (models.Attachment, error) {
attachment.Uuid = uuid.NewString()
attachment.Rid = RandString(16)
attachment.Size = file.Size
@@ -143,39 +153,25 @@ func NewAttachmentMetadata(tx *gorm.DB, user models.Account, file *multipart.Fil
if err := tx.Save(&attachment).Error; err != nil {
return attachment, fmt.Errorf("failed to save attachment record: %v", err)
} else {
CacheAttachment(attachment)
}
return attachment, nil
}
func NewAttachmentPlaceholder(tx *gorm.DB, user models.Account, attachment models.Attachment) (models.Attachment, error) {
func NewRefURLAttachment(tx *gorm.DB, user *sec.UserInfo, attachment models.Attachment) (models.Attachment, error) {
if attachment.RefURL == nil {
return attachment, fmt.Errorf("attachment doesn't have a ref url")
}
attachment.Uuid = uuid.NewString()
attachment.Rid = RandString(16)
attachment.IsUploaded = false
attachment.FileChunks = datatypes.JSONMap{}
attachment.Size = 0
attachment.Destination = models.AttachmentDstExternal
attachment.Type = models.AttachmentTypeNormal
attachment.AccountID = user.ID
chunkSize := viper.GetInt64("performance.file_chunk_size")
chunkCount := math.Ceil(float64(attachment.Size) / float64(chunkSize))
for idx := 0; idx < int(chunkCount); idx++ {
cid := RandString(8)
attachment.FileChunks[cid] = idx
}
// If the user didn't provide file mimetype manually, we have to detect it
if len(attachment.MimeType) == 0 {
if ext := filepath.Ext(attachment.Name); len(ext) > 0 {
// Detect mimetype by file extensions
attachment.MimeType = mime.TypeByExtension(ext)
}
}
if err := tx.Save(&attachment).Error; err != nil {
return attachment, fmt.Errorf("failed to save attachment record: %v", err)
} else {
CacheAttachment(attachment)
}
return attachment, nil
@@ -194,49 +190,43 @@ func TryLinkAttachment(tx *gorm.DB, og models.Attachment, hash string) (bool, er
}
}
prev.RefCount++
og.RefID = &prev.ID
og.Uuid = prev.Uuid
og.Destination = prev.Destination
if og.AccountID == prev.AccountID {
og.IsSelfRef = true
}
if err := tx.Save(&og).Error; err != nil {
if err := tx.Model(&og).Updates(&models.Attachment{
RefID: &prev.ID,
Uuid: prev.Uuid,
Destination: prev.Destination,
IsSelfRef: og.AccountID == prev.AccountID,
}).Error; err != nil {
tx.Rollback()
return true, err
} else if err = tx.Save(&prev).Error; err != nil {
} else if err = tx.Model(&prev).Update("ref_count", prev.RefCount+1).Error; err != nil {
tx.Rollback()
return true, err
}
CacheAttachment(prev)
CacheAttachment(og)
return true, nil
}
func UpdateAttachment(item models.Attachment) (models.Attachment, error) {
if err := database.C.Updates(&item).Error; err != nil {
if err := database.C.Save(&item).Error; err != nil {
return item, err
} else {
CacheAttachment(item)
}
return item, nil
}
func DeleteAttachment(item models.Attachment) error {
func DeleteAttachment(item models.Attachment, txs ...*gorm.DB) error {
dat := item
tx := database.C.Begin()
var tx *gorm.DB
if len(txs) == 0 {
tx = database.C.Begin()
} else {
tx = txs[0]
}
if item.RefID != nil {
var refTarget models.Attachment
if err := database.C.Where(models.Attachment{
BaseModel: hyper.BaseModel{ID: *item.RefID},
}).First(&refTarget).Error; err == nil {
if err := database.C.Where("id = ?", *item.RefID).First(&refTarget).Error; err == nil {
refTarget.RefCount--
if err := tx.Save(&refTarget).Error; err != nil {
tx.Rollback()
@@ -244,21 +234,113 @@ func DeleteAttachment(item models.Attachment) error {
}
}
}
if item.Thumbnail != nil {
if err := DeleteAttachment(*item.Thumbnail, tx); err != nil {
return err
}
}
if item.Compressed != nil {
if err := DeleteAttachment(*item.Compressed, tx); err != nil {
return err
}
}
if err := database.C.Delete(&item).Error; err != nil {
tx.Rollback()
return err
} else {
cacheManager := cache.New[any](localCache.S)
marshal := marshaler.New(cacheManager)
contx := context.Background()
marshal.Delete(contx, GetAttachmentCacheKey(item.Rid))
cachekit.Delete(gap.Ca, KgAttachmentCache(item.Rid))
}
tx.Commit()
if dat.RefCount == 0 {
PublishDeleteFileTask(dat)
go fs.DeleteFile(dat)
}
return nil
}
func DeleteAttachmentInBatch(items []models.Attachment, txs ...*gorm.DB) error {
if len(items) == 0 {
return nil
}
var tx *gorm.DB
if len(txs) == 0 {
tx = database.C.Begin()
} else {
tx = txs[0]
}
refIDs := []uint{}
for _, item := range items {
if item.RefID != nil {
refIDs = append(refIDs, *item.RefID)
}
}
if len(refIDs) > 0 {
var refTargets []models.Attachment
if err := tx.Where("id IN ?", refIDs).Find(&refTargets).Error; err == nil {
for i := range refTargets {
refTargets[i].RefCount--
}
if err := tx.Save(&refTargets).Error; err != nil {
tx.Rollback()
return fmt.Errorf("unable to update ref count: %v", err)
}
}
}
var subAttachments []models.Attachment
for _, item := range items {
if item.Thumbnail != nil {
subAttachments = append(subAttachments, *item.Thumbnail)
}
if item.Compressed != nil {
subAttachments = append(subAttachments, *item.Compressed)
}
}
if len(subAttachments) > 0 {
if err := DeleteAttachmentInBatch(subAttachments, tx); err != nil {
tx.Rollback()
return err
}
}
rids := make([]string, len(items))
for i, item := range items {
rids[i] = item.Rid
}
if err := tx.Where("rid IN ?", rids).Delete(&models.Attachment{}).Error; err != nil {
tx.Rollback()
return err
}
for _, rid := range rids {
cachekit.Delete(gap.Ca, KgAttachmentCache(rid))
}
tx.Commit()
go func() {
for _, item := range items {
if item.RefCount == 0 {
fs.DeleteFile(item)
}
}
}()
return nil
}
func CountAttachmentUsage(tx *gorm.DB, delta int) (int64, error) {
if tx := tx.Model(&models.Attachment{}).
Update("used_count", gorm.Expr("used_count + ?", delta)); tx.Error != nil {
return tx.RowsAffected, tx.Error
} else {
return tx.RowsAffected, nil
}
}

View File

@@ -0,0 +1,150 @@
package services
import (
"encoding/json"
"fmt"
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/fs"
jsoniter "github.com/json-iterator/go"
"github.com/rs/zerolog/log"
"github.com/spf13/cast"
"github.com/spf13/viper"
)
func CountBoostByUser(userId uint) (int64, error) {
var count int64
if err := database.C.
Model(&models.AttachmentBoost{}).
Where("account_id = ?", userId).
Count(&count).Error; err != nil {
return count, err
}
return count, nil
}
func ListBoostByUser(userId uint, take, offset int) ([]models.AttachmentBoost, error) {
var boosts []models.AttachmentBoost
if err := database.C.
Where("account_id = ?", userId).
Limit(take).Offset(offset).
Find(&boosts).Error; err != nil {
return boosts, err
}
return boosts, nil
}
func ListBoostByAttachment(attachmentId uint) ([]models.AttachmentBoost, error) {
var boosts []models.AttachmentBoost
if err := database.C.Where("attachment_id = ?", attachmentId).Find(&boosts).Error; err != nil {
return boosts, err
}
return boosts, nil
}
func ListBoostByAttachmentWithStatus(attachmentId uint, status int) ([]models.AttachmentBoost, error) {
var boosts []models.AttachmentBoost
if err := database.C.
Where("attachment_id = ? AND status = ?", attachmentId, status).
Find(&boosts).Error; err != nil {
return boosts, err
}
return boosts, nil
}
func GetBoostByID(id uint) (models.AttachmentBoost, error) {
var boost models.AttachmentBoost
if err := database.C.
Where("id = ?", id).
Preload("Attachment").
First(&boost).Error; err != nil {
return boost, err
}
return boost, nil
}
func CreateBoost(user *sec.UserInfo, source models.Attachment, destination int) (models.AttachmentBoost, error) {
var boost models.AttachmentBoost
if err := database.C.Where("attachment_id = ? AND destination = ?", source.ID, destination).First(&boost); err == nil {
return boost, fmt.Errorf("boost already exists")
}
boost = models.AttachmentBoost{
Status: models.BoostStatusPending,
Destination: destination,
AttachmentID: source.ID,
Attachment: source,
AccountID: user.ID,
}
if des, ok := DestinationsByIndex[destination]; !ok {
return boost, fmt.Errorf("invalid destination: %d", destination)
} else {
var destBase models.BaseDestination
json.Unmarshal(des.Raw, &destBase)
if !destBase.IsBoost {
return boost, fmt.Errorf("invalid destination: %d; wasn't available for boost", destination)
}
}
if err := database.C.Save(&boost).Error; err != nil {
return boost, err
}
boost.Attachment = source
go ActivateBoost(boost)
return boost, nil
}
func ActivateBoost(boost models.AttachmentBoost) error {
log.Debug().Any("boost", boost).Msg("Activating boost...")
dests := cast.ToSlice(viper.Get("destinations"))
if boost.Destination >= len(dests) {
log.Warn().Any("boost", boost).Msg("Unable to activate boost, invalid destination...")
database.C.Model(&boost).Update("status", models.BoostStatusError)
return fmt.Errorf("invalid destination: %d", boost.Destination)
}
if err := ReUploadFile(boost.Attachment, boost.Destination, true); err != nil {
log.Warn().Any("boost", boost).Err(err).Msg("Unable to activate boost...")
database.C.Model(&boost).Update("status", models.BoostStatusError)
return err
}
log.Info().Any("boost", boost).Msg("Boost was activated successfully.")
database.C.Model(&boost).Update("status", models.BoostStatusActive)
return nil
}
func UpdateBoostStatus(boost models.AttachmentBoost, status int) (models.AttachmentBoost, error) {
if status != models.BoostStatusActive && status != models.BoostStatusSuspended {
return boost, fmt.Errorf("invalid status: %d", status)
}
err := database.C.Save(&boost).Error
return boost, err
}
func DeleteBoost(boost models.AttachmentBoost) error {
destMap := viper.GetStringMap(fmt.Sprintf("destinations.%d", boost.Destination))
var dest models.BaseDestination
rawDest, _ := jsoniter.Marshal(destMap)
_ = jsoniter.Unmarshal(rawDest, &dest)
switch dest.Type {
case models.DestinationTypeLocal:
var destConfigured models.LocalDestination
_ = jsoniter.Unmarshal(rawDest, &destConfigured)
return fs.DeleteFileFromLocal(destConfigured, boost.Attachment.Uuid)
case models.DestinationTypeS3:
var destConfigured models.S3Destination
_ = jsoniter.Unmarshal(rawDest, &destConfigured)
return fs.DeleteFileFromS3(destConfigured, boost.Attachment.Uuid)
default:
return fmt.Errorf("invalid destination: unsupported protocol %s", dest.Type)
}
}

View File

@@ -1,24 +1,28 @@
package services
import (
database2 "git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
"time"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"github.com/rs/zerolog/log"
)
func DoAutoDatabaseCleanup() {
deadline := time.Now().Add(60 * time.Minute)
log.Debug().Time("deadline", deadline).Msg("Now cleaning up entire database...")
func DoUnusedAttachmentCleanup() {
deadline := time.Now().Add(-60 * time.Minute)
var count int64
for _, model := range database2.AutoMaintainRange {
tx := database2.C.Unscoped().Delete(model, "deleted_at >= ?", deadline)
if tx.Error != nil {
log.Error().Err(tx.Error).Msg("An error occurred when running auth context cleanup...")
}
count += tx.RowsAffected
var result []models.Attachment
if err := database.C.Where("created_at < ? AND used_count = 0", deadline).
Find(&result).Error; err != nil {
log.Error().Err(err).Msg("An error occurred when getting unused attachments...")
return
}
log.Debug().Int64("affected", count).Msg("Clean up entire database accomplished.")
if err := DeleteAttachmentInBatch(result); err != nil {
log.Error().Err(err).Msg("An error occurred when deleting unused attachments...")
return
}
log.Info().Int("count", len(result)).Msg("Deleted unused attachments...")
}

View File

@@ -0,0 +1,43 @@
package services
import (
"fmt"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
jsoniter "github.com/json-iterator/go"
"github.com/rs/zerolog/log"
"github.com/spf13/cast"
"github.com/spf13/viper"
)
type destinationMapping struct {
Index int
Raw []byte
}
var (
DestinationsByIndex = make(map[int]destinationMapping)
DestinationsByRegion = make(map[string]destinationMapping)
)
func BuildDestinationMapping() {
count := len(cast.ToSlice(viper.Get("destinations")))
for idx := 0; idx < count; idx++ {
destMap := viper.GetStringMap(fmt.Sprintf("destinations.%d", idx))
var parsed models.BaseDestination
raw, _ := jsoniter.Marshal(destMap)
_ = jsoniter.Unmarshal(raw, &parsed)
mapping := destinationMapping{
Index: idx,
Raw: raw,
}
if len(parsed.Region) > 0 {
DestinationsByIndex[idx] = mapping
DestinationsByRegion[parsed.Region] = mapping
}
}
log.Info().Int("count", count).Msg("Destinations mapping built")
}

View File

@@ -0,0 +1,150 @@
package services
import (
"errors"
"fmt"
"math"
"mime"
"mime/multipart"
"os"
"path/filepath"
"time"
"git.solsynth.dev/hypernet/nexus/pkg/nex/cachekit"
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/gap"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
jsoniter "github.com/json-iterator/go"
"github.com/spf13/viper"
"gorm.io/datatypes"
"gorm.io/gorm"
)
func KgAttachmentFragmentCache(rid string) string {
return cachekit.FKey("attachment-fragment", rid)
}
func NewAttachmentFragment(tx *gorm.DB, user *sec.UserInfo, fragment models.AttachmentFragment) (models.AttachmentFragment, error) {
if fragment.Fingerprint != nil {
var existsFragment models.AttachmentFragment
if err := database.C.Where(models.AttachmentFragment{
Fingerprint: fragment.Fingerprint,
AccountID: user.ID,
}).First(&existsFragment).Error; err == nil {
return existsFragment, nil
}
}
fragment.Uuid = uuid.NewString()
fragment.Rid = RandString(16)
fragment.FileChunks = datatypes.JSONMap{}
fragment.AccountID = user.ID
chunkSize := viper.GetInt64("performance.file_chunk_size")
chunkCount := math.Ceil(float64(fragment.Size) / float64(chunkSize))
for idx := 0; idx < int(chunkCount); idx++ {
cid := RandString(8)
fragment.FileChunks[cid] = idx
}
// If the user didn't provide file mimetype manually, we have to detect it
if len(fragment.MimeType) == 0 {
if ext := filepath.Ext(fragment.Name); len(ext) > 0 {
// Detect mimetype by file extensions
fragment.MimeType = mime.TypeByExtension(ext)
}
}
if err := tx.Save(&fragment).Error; err != nil {
return fragment, fmt.Errorf("failed to save attachment record: %v", err)
}
return fragment, nil
}
func GetFragmentByRID(rid string) (models.AttachmentFragment, error) {
if val, err := cachekit.Get[models.AttachmentFragment](
gap.Ca,
KgAttachmentFragmentCache(rid),
); err == nil {
return val, nil
}
var attachment models.AttachmentFragment
if err := database.C.Where(models.AttachmentFragment{
Rid: rid,
}).Preload("Pool").First(&attachment).Error; err != nil {
return attachment, err
} else {
CacheAttachmentFragment(attachment)
}
return attachment, nil
}
func CacheAttachmentFragment(item models.AttachmentFragment) {
cachekit.Set[models.AttachmentFragment](
gap.Ca,
KgAttachmentFragmentCache(item.Rid),
item,
60*time.Minute,
)
}
func UploadFragmentChunk(ctx *fiber.Ctx, cid string, file *multipart.FileHeader, meta models.AttachmentFragment) error {
destMap := viper.GetStringMap("destinations.0")
var dest models.LocalDestination
rawDest, _ := jsoniter.Marshal(destMap)
_ = jsoniter.Unmarshal(rawDest, &dest)
tempPath := filepath.Join(dest.Path, fmt.Sprintf("%s.part%s.partial", meta.Uuid, cid))
destPath := filepath.Join(dest.Path, fmt.Sprintf("%s.part%s", meta.Uuid, cid))
if err := ctx.SaveFile(file, tempPath); err != nil {
return err
}
return os.Rename(tempPath, destPath)
}
func UploadFragmentChunkBytes(ctx *fiber.Ctx, cid string, raw []byte, meta models.AttachmentFragment) error {
destMap := viper.GetStringMap("destinations.0")
var dest models.LocalDestination
rawDest, _ := jsoniter.Marshal(destMap)
_ = jsoniter.Unmarshal(rawDest, &dest)
tempPath := filepath.Join(dest.Path, fmt.Sprintf("%s.part%s.partial", meta.Uuid, cid))
destPath := filepath.Join(dest.Path, fmt.Sprintf("%s.part%s", meta.Uuid, cid))
if err := os.WriteFile(tempPath, raw, 0644); err != nil {
return err
}
return os.Rename(tempPath, destPath)
}
func CheckFragmentChunkExists(meta models.AttachmentFragment, cid string) bool {
destMap := viper.GetStringMap("destinations.0")
var dest models.LocalDestination
rawDest, _ := jsoniter.Marshal(destMap)
_ = jsoniter.Unmarshal(rawDest, &dest)
path := filepath.Join(dest.Path, fmt.Sprintf("%s.part%s", meta.Uuid, cid))
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
return false
} else {
return true
}
}
func FindFragmentMissingChunks(meta models.AttachmentFragment) []string {
var missing []string
for cid := range meta.FileChunks {
if !CheckFragmentChunkExists(meta, cid) {
missing = append(missing, cid)
}
}
return missing
}

View File

@@ -0,0 +1,180 @@
package services
import (
"context"
"encoding/json"
"fmt"
"math/rand/v2"
nurl "net/url"
"path/filepath"
"strings"
"time"
"git.solsynth.dev/hypernet/nexus/pkg/nex/cachekit"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/gap"
jsoniter "github.com/json-iterator/go"
"github.com/minio/minio-go/v7"
"github.com/samber/lo"
"github.com/spf13/viper"
)
type openAttachmentResult struct {
Attachment models.Attachment `json:"attachment"`
Boosts []models.AttachmentBoost `json:"boost"`
}
func KgAttachmentOpenCache(rid string) string {
return fmt.Sprintf("attachment-open#%s", rid)
}
func OpenAttachmentByRID(rid string, preferredSize int, region ...string) (url string, filesize int64, mimetype string, err error) {
var result *openAttachmentResult
if val, err := cachekit.Get[openAttachmentResult](
gap.Ca,
KgAttachmentOpenCache(rid),
); err == nil {
result = &val
}
if result == nil {
var attachment models.Attachment
if err = database.C.Where(models.Attachment{
Rid: rid,
}).
Preload("Pool").
Preload("Thumbnail").
Preload("Compressed").
First(&attachment).Error; err != nil {
return
}
var boosts []models.AttachmentBoost
boosts, err = ListBoostByAttachmentWithStatus(attachment.ID, models.BoostStatusActive)
if err != nil {
return
}
result = &openAttachmentResult{
Attachment: attachment,
Boosts: boosts,
}
}
if len(result.Attachment.MimeType) > 0 {
mimetype = result.Attachment.MimeType
}
if result.Attachment.RefURL != nil {
url = *result.Attachment.RefURL
filesize = 0
return
}
filesize = result.Attachment.Size
var dest models.BaseDestination
var rawDest []byte
if len(region) > 0 {
if des, ok := DestinationsByRegion[region[0]]; ok {
for _, boost := range result.Boosts {
if boost.Destination == des.Index {
rawDest = des.Raw
json.Unmarshal(rawDest, &dest)
}
}
}
}
if rawDest == nil {
if len(result.Boosts) > 0 {
randomIdx := rand.IntN(len(result.Boosts))
boost := result.Boosts[randomIdx]
if des, ok := DestinationsByIndex[boost.Destination]; ok {
rawDest = des.Raw
json.Unmarshal(rawDest, &dest)
}
} else {
if des, ok := DestinationsByIndex[result.Attachment.Destination]; ok {
rawDest = des.Raw
json.Unmarshal(rawDest, &dest)
}
}
}
if rawDest == nil {
err = fmt.Errorf("no destination found")
return
}
switch dest.Type {
case models.DestinationTypeLocal:
var destConfigured models.LocalDestination
_ = jsoniter.Unmarshal(rawDest, &destConfigured)
url = "file://" + filepath.Join(destConfigured.Path, result.Attachment.Uuid)
return
case models.DestinationTypeS3:
var destConfigured models.S3Destination
_ = jsoniter.Unmarshal(rawDest, &destConfigured)
if len(destConfigured.AccessBaseURL) > 0 {
url = fmt.Sprintf(
"%s/%s",
destConfigured.AccessBaseURL,
nurl.QueryEscape(filepath.Join(destConfigured.Path, result.Attachment.Uuid)),
)
} else if destConfigured.EnableSigned {
var client *minio.Client
client, err = destConfigured.GetClient()
if err != nil {
return
}
var uri *nurl.URL
uri, err = client.PresignedGetObject(context.Background(), destConfigured.Bucket, result.Attachment.Uuid, 60*time.Minute, nil)
if err != nil {
return
}
url = uri.String()
} else {
protocol := lo.Ternary(destConfigured.EnableSSL, "https", "http")
url = fmt.Sprintf(
"%s://%s.%s/%s",
protocol,
destConfigured.Bucket,
destConfigured.Endpoint,
nurl.QueryEscape(filepath.Join(destConfigured.Path, result.Attachment.Uuid)),
)
}
if strings.HasPrefix(mimetype, "image") && filesize >= viper.GetInt64("traffic.minimum_size") {
if len(destConfigured.ImageProxyURL) > 0 && preferredSize > 0 {
url = fmt.Sprintf(
"%s/%dx%d,fit/%s",
destConfigured.ImageProxyURL,
preferredSize,
preferredSize,
url,
)
filesize = int64(preferredSize * preferredSize)
}
}
return
default:
err = fmt.Errorf("invalid destination: unsupported protocol %s", dest.Type)
return
}
}
func CacheOpenAttachment(item *openAttachmentResult) {
if item == nil {
return
}
cachekit.Set[openAttachmentResult](
gap.Ca,
KgAttachmentCache(item.Attachment.Rid),
*item,
60*time.Minute,
)
}

View File

@@ -0,0 +1,77 @@
package services
import (
"context"
"fmt"
"time"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/gap"
wproto "git.solsynth.dev/hypernet/wallet/pkg/proto"
"github.com/rs/zerolog/log"
"github.com/samber/lo"
"github.com/spf13/viper"
)
func GetLastDayUploadedBytes(user uint) (int64, error) {
deadline := time.Now().Add(-24 * time.Hour)
var totalSize int64
if err := database.C.
Model(&models.Attachment{}).
Where("account_id = ?", user).
Where("created_at >= ?", deadline).
Select("SUM(size)").
Scan(&totalSize).Error; err != nil {
return totalSize, err
}
return totalSize, nil
}
// PlaceOrder create a transaction if needed for user
// Pricing according here: https://kb.solsynth.dev/solar-network/wallet#file-uploads
func PlaceOrder(user uint, filesize int64, withDiscount bool) error {
currentBytes, _ := GetLastDayUploadedBytes(user)
discountFileSize := viper.GetInt64("payment.discount")
if currentBytes+filesize <= discountFileSize {
// Discount included
return nil
}
var amount float64
if withDiscount {
amount = float64(filesize) / 1024 / 1024 * 1
} else {
amount = float64(filesize) / 1024 / 1024 * 1
}
if !withDiscount {
amount += 10 // Service fee
}
conn, err := gap.Nx.GetClientGrpcConn("wa")
if err != nil {
return fmt.Errorf("unable to connect wallet: %v", err)
}
wc := wproto.NewPaymentServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
resp, err := wc.MakeTransactionWithAccount(ctx, &wproto.MakeTransactionWithAccountRequest{
PayerAccountId: lo.ToPtr(uint64(user)),
Amount: amount,
Remark: "File Uploading Fee",
Currency: "normal",
})
if err != nil {
return err
}
log.Info().
Uint64("transaction", resp.Id).Float64("amount", amount).Bool("discount", withDiscount).
Msg("Order placed for charge file uploading fee...")
return nil
}

View File

@@ -1,8 +1,8 @@
package services
import (
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
)
func ListAttachmentPool() ([]models.AttachmentPool, error) {

View File

@@ -0,0 +1,53 @@
package services
import (
"fmt"
"strings"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
)
func SetAttachmentAsThumbnail(item models.Attachment) (models.Attachment, error) {
if !strings.HasPrefix(item.MimeType, "image") {
return item, fmt.Errorf("thumbnail must be an image")
}
item.Type = models.AttachmentTypeThumbnail
item.UsedCount++
if err := database.C.Save(&item).Error; err != nil {
return item, err
}
return item, nil
}
func SetAttachmentAsCompressed(item models.Attachment) (models.Attachment, error) {
item.Type = models.AttachmentTypeCompressed
item.UsedCount++
if err := database.C.Save(&item).Error; err != nil {
return item, err
}
return item, nil
}
func UnsetAttachmentAsThumbnail(item models.Attachment) (models.Attachment, error) {
item.Type = models.AttachmentTypeNormal
item.UsedCount--
if err := database.C.Save(&item).Error; err != nil {
return item, err
}
return item, nil
}
func UnsetAttachmentAsCompressed(item models.Attachment) (models.Attachment, error) {
item.Type = models.AttachmentTypeNormal
item.UsedCount--
if err := database.C.Save(&item).Error; err != nil {
return item, err
}
return item, nil
}

View File

@@ -1,8 +1,9 @@
package services
import (
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"gorm.io/gorm"
)
@@ -30,7 +31,7 @@ func ListStickerPackWithStickers(tx *gorm.DB, take, offset int) ([]models.Sticke
return packs, nil
}
func NewStickerPack(user models.Account, prefix, name, desc string) (models.StickerPack, error) {
func NewStickerPack(user *sec.UserInfo, prefix, name, desc string) (models.StickerPack, error) {
pack := models.StickerPack{
Prefix: prefix,
Name: name,

View File

@@ -1,10 +1,13 @@
package services
import (
"errors"
"fmt"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
"gorm.io/gorm"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"github.com/spf13/viper"
)
@@ -71,3 +74,36 @@ func DeleteSticker(sticker models.Sticker) (models.Sticker, error) {
}
return sticker, nil
}
func AddStickerPack(user uint, pack models.StickerPack) (models.StickerPackOwnership, error) {
var ownership models.StickerPackOwnership
if err := database.C.
Where("account_id = ? AND pack_id = ?", user, pack.ID).
First(&ownership).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return ownership, fmt.Errorf("unable to get current ownership: %v", err)
} else if err == nil {
return ownership, fmt.Errorf("you already own this pack")
}
ownership = models.StickerPackOwnership{
AccountID: user,
PackID: pack.ID,
}
err := database.C.Save(&ownership).Error
return ownership, err
}
func RemoveStickerPack(user uint, pack models.StickerPack) (models.StickerPackOwnership, error) {
var ownership models.StickerPackOwnership
if err := database.C.
Where("account_id = ? AND pack_id = ?", user, pack.ID).
First(&ownership).Error; err != nil {
return ownership, fmt.Errorf("unable to get current ownership: %v", err)
}
err := database.C.Delete(&ownership).Error
return ownership, err
}

View File

@@ -2,24 +2,23 @@ package services
import (
"context"
"errors"
"fmt"
"io"
"mime/multipart"
"os"
"path/filepath"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/fs"
"github.com/gofiber/fiber/v2"
jsoniter "github.com/json-iterator/go"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/spf13/viper"
)
func UploadFileToTemporary(ctx *fiber.Ctx, file *multipart.FileHeader, meta models.Attachment) error {
destMap := viper.GetStringMap("destinations.temporary")
destMap := viper.GetStringMap(fmt.Sprintf("destinations.%d", meta.Destination))
var dest models.BaseDestination
rawDest, _ := jsoniter.Marshal(destMap)
@@ -35,66 +34,34 @@ func UploadFileToTemporary(ctx *fiber.Ctx, file *multipart.FileHeader, meta mode
}
}
func UploadChunkToTemporary(ctx *fiber.Ctx, cid string, file *multipart.FileHeader, meta models.Attachment) error {
destMap := viper.GetStringMap("destinations.temporary")
func ReUploadFile(meta models.Attachment, dst int, doNotUpdate ...bool) error {
if meta.Destination == dst {
return fmt.Errorf("destnation cannot be reversed temporary or the same as the original")
}
var dest models.BaseDestination
rawDest, _ := jsoniter.Marshal(destMap)
_ = jsoniter.Unmarshal(rawDest, &dest)
prevDst := meta.Destination
inDst, err := fs.DownloadFileToLocal(meta, prevDst)
if err != nil {
return fmt.Errorf("unable to retrieve file content: %v", err)
}
switch dest.Type {
case models.DestinationTypeLocal:
var destConfigured models.LocalDestination
_ = jsoniter.Unmarshal(rawDest, &destConfigured)
tempPath := filepath.Join(destConfigured.Path, fmt.Sprintf("%s.part%s.partial", meta.Uuid, cid))
destPath := filepath.Join(destConfigured.Path, fmt.Sprintf("%s.part%s", meta.Uuid, cid))
if err := ctx.SaveFile(file, tempPath); err != nil {
return err
cleanupDst := func() {
if len(doNotUpdate) == 0 || !doNotUpdate[0] {
database.C.Model(&meta).Update("destination", dst)
}
return os.Rename(tempPath, destPath)
default:
return fmt.Errorf("invalid destination: unsupported protocol %s", dest.Type)
}
}
func CheckChunkExistsInTemporary(meta models.Attachment, cid string) bool {
destMap := viper.GetStringMap("destinations.temporary")
var dest models.LocalDestination
rawDest, _ := jsoniter.Marshal(destMap)
_ = jsoniter.Unmarshal(rawDest, &dest)
path := filepath.Join(dest.Path, fmt.Sprintf("%s.part%s", meta.Uuid, cid))
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
return false
} else {
return true
}
}
func ReUploadFileToPermanent(meta models.Attachment) error {
if meta.Destination != models.AttachmentDstTemporary {
return fmt.Errorf("attachment isn't in temporary storage, unable to process")
if prevDst == models.AttachmentDstTemporary {
return
}
os.Remove(inDst)
}
meta.Destination = models.AttachmentDstPermanent
destMap := viper.GetStringMap("destinations.permanent")
meta.Destination = dst
destMap := viper.GetStringMap(fmt.Sprintf("destinations.%d", dst))
var dest models.BaseDestination
rawDest, _ := jsoniter.Marshal(destMap)
_ = jsoniter.Unmarshal(rawDest, &dest)
prevDestMap := viper.GetStringMap("destinations.temporary")
// Currently, the temporary destination only supports the local.
// So we can do this.
var prevDest models.LocalDestination
prevRawDest, _ := jsoniter.Marshal(prevDestMap)
_ = jsoniter.Unmarshal(prevRawDest, &prevDest)
inDst := filepath.Join(prevDest.Path, meta.Uuid)
switch dest.Type {
case models.DestinationTypeLocal:
var destConfigured models.LocalDestination
@@ -117,32 +84,30 @@ func ReUploadFileToPermanent(meta models.Attachment) error {
return fmt.Errorf("unable to copy data to dest file: %v", err)
}
database.C.Save(&meta)
CacheAttachment(meta)
cleanupDst()
return nil
case models.DestinationTypeS3:
var destConfigured models.S3Destination
_ = jsoniter.Unmarshal(rawDest, &destConfigured)
client, err := minio.New(destConfigured.Endpoint, &minio.Options{
Creds: credentials.NewStaticV4(destConfigured.SecretID, destConfigured.SecretKey, ""),
Secure: destConfigured.EnableSSL,
})
client, err := destConfigured.GetClient()
if err != nil {
return fmt.Errorf("unable to configure s3 client: %v", err)
}
_, err = client.FPutObject(context.Background(), destConfigured.Bucket, filepath.Join(destConfigured.Path, meta.Uuid), inDst, minio.PutObjectOptions{
ContentType: meta.MimeType,
SendContentMd5: false,
DisableContentSha256: true,
ContentType: meta.MimeType,
SendContentMd5: false,
DisableContentSha256: true,
PartSize: 10 * 1024 * 1024,
ConcurrentStreamParts: true,
NumThreads: 4,
})
if err != nil {
return fmt.Errorf("unable to upload file to s3: %v", err)
}
database.C.Save(&meta)
CacheAttachment(meta)
cleanupDst()
return nil
default:
return fmt.Errorf("invalid destination: unsupported protocol %s", dest.Type)

View File

@@ -1,20 +1,24 @@
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/cache"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/gap"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/grpc"
"git.solsynth.dev/hypernet/nexus/pkg/nex/sec"
pkg "git.solsynth.dev/hypernet/paperclip/pkg/internal"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/fs"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/gap"
"github.com/fatih/color"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/server"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/services"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/database"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/grpc"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/server"
"git.solsynth.dev/hypernet/paperclip/pkg/internal/services"
"github.com/robfig/cron/v3"
pkg "git.solsynth.dev/hydrogen/paperclip/pkg/internal"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
@@ -26,6 +30,12 @@ func init() {
}
func main() {
// Booting screen
fmt.Println(color.YellowString(" ____ _ _\n| _ \\ __ _ _ __ ___ _ __ ___| (_)_ __\n| |_) / _` | '_ \\ / _ \\ '__/ __| | | '_ \\\n| __/ (_| | |_) | __/ | | (__| | | |_) |\n|_| \\__,_| .__/ \\___|_| \\___|_|_| .__/\n |_| |_|"))
fmt.Printf("%s v%s\n", color.New(color.FgHiYellow).Add(color.Bold).Sprintf("Hypernet.Paperclip"), pkg.AppVersion)
fmt.Printf("The upload service in Hypernet\n")
color.HiBlack("=====================================================\n")
// Configure settings
viper.AddConfigPath(".")
viper.AddConfigPath("..")
@@ -37,58 +47,54 @@ func main() {
log.Panic().Err(err).Msg("An error occurred when loading settings.")
}
// Connect to nexus
if err := gap.InitializeToNexus(); err != nil {
log.Error().Err(err).Msg("An error occurred when registering service to nexus...")
}
// Load keypair
if reader, err := sec.NewInternalTokenReader(viper.GetString("security.internal_public_key")); err != nil {
log.Error().Err(err).Msg("An error occurred when reading internal public key for jwt. Authentication related features will be disabled.")
} else {
server.IReader = reader
log.Info().Msg("Internal jwt public key loaded.")
}
// Connect to database
if err := database.NewSource(); err != nil {
if err := database.NewGorm(); err != nil {
log.Fatal().Err(err).Msg("An error occurred when connect to database.")
} else if err := database.RunMigration(database.C); err != nil {
log.Fatal().Err(err).Msg("An error occurred when running database auto migration.")
}
// Initialize cache
if err := cache.NewStore(); err != nil {
log.Fatal().Err(err).Msg("An error occurred when initializing cache.")
}
// Connect other services
if err := gap.RegisterService(); err != nil {
log.Error().Err(err).Msg("An error occurred when registering service to dealer...")
}
// Set up some workers
for idx := 0; idx < viper.GetInt("workers.files_deletion"); idx++ {
go services.StartConsumeDeletionTask()
}
for idx := 0; idx < viper.GetInt("workers.files_analyze"); idx++ {
go services.StartConsumeAnalyzeTask()
}
// Configure timed tasks
quartz := cron.New(cron.WithLogger(cron.VerbosePrintfLogger(&log.Logger)))
quartz.AddFunc("@every 60m", services.DoAutoDatabaseCleanup)
quartz.AddFunc("@every 60m", services.RunMarkLifecycleDeletionTask)
quartz.AddFunc("@every 60m", services.RunMarkMultipartDeletionTask)
quartz.AddFunc("@midnight", services.RunScheduleDeletionTask)
// quartz.AddFunc("@every 60m", services.DoUnusedAttachmentCleanup)
quartz.AddFunc("@every 60m", fs.RunMarkLifecycleDeletionTask)
quartz.AddFunc("@every 60m", fs.RunMarkMultipartDeletionTask)
quartz.AddFunc("@midnight", fs.RunScheduleDeletionTask)
quartz.Start()
// Server
server.NewServer()
go server.Listen()
go server.NewServer().Listen()
// Grpc Server
grpc.NewGRPC()
go grpc.ListenGRPC()
go grpc.NewGrpc().Listen()
// Post-boot actions
services.BuildDestinationMapping()
services.ScanUnanalyzedFileFromDatabase()
fs.RunMarkLifecycleDeletionTask()
// Messages
log.Info().Msgf("Paperclip v%s is started...", pkg.AppVersion)
services.ScanUnanalyzedFileFromDatabase()
services.RunMarkLifecycleDeletionTask()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Info().Msgf("Paperclip v%s is quitting...", pkg.AppVersion)
quartz.Stop()
}

678
pkg/proto/attachment.pb.go Normal file
View File

@@ -0,0 +1,678 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.6
// protoc v5.29.3
// source: attachment.proto
package proto
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type GetAttachmentRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id *uint64 `protobuf:"varint,1,opt,name=id,proto3,oneof" json:"id,omitempty"`
Rid *string `protobuf:"bytes,2,opt,name=rid,proto3,oneof" json:"rid,omitempty"`
UserId *uint64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3,oneof" json:"user_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GetAttachmentRequest) Reset() {
*x = GetAttachmentRequest{}
mi := &file_attachment_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetAttachmentRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetAttachmentRequest) ProtoMessage() {}
func (x *GetAttachmentRequest) ProtoReflect() protoreflect.Message {
mi := &file_attachment_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetAttachmentRequest.ProtoReflect.Descriptor instead.
func (*GetAttachmentRequest) Descriptor() ([]byte, []int) {
return file_attachment_proto_rawDescGZIP(), []int{0}
}
func (x *GetAttachmentRequest) GetId() uint64 {
if x != nil && x.Id != nil {
return *x.Id
}
return 0
}
func (x *GetAttachmentRequest) GetRid() string {
if x != nil && x.Rid != nil {
return *x.Rid
}
return ""
}
func (x *GetAttachmentRequest) GetUserId() uint64 {
if x != nil && x.UserId != nil {
return *x.UserId
}
return 0
}
type GetAttachmentResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Attachment []byte `protobuf:"bytes,1,opt,name=attachment,proto3,oneof" json:"attachment,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GetAttachmentResponse) Reset() {
*x = GetAttachmentResponse{}
mi := &file_attachment_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetAttachmentResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetAttachmentResponse) ProtoMessage() {}
func (x *GetAttachmentResponse) ProtoReflect() protoreflect.Message {
mi := &file_attachment_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetAttachmentResponse.ProtoReflect.Descriptor instead.
func (*GetAttachmentResponse) Descriptor() ([]byte, []int) {
return file_attachment_proto_rawDescGZIP(), []int{1}
}
func (x *GetAttachmentResponse) GetAttachment() []byte {
if x != nil {
return x.Attachment
}
return nil
}
type ListAttachmentRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id []uint64 `protobuf:"varint,1,rep,packed,name=id,proto3" json:"id,omitempty"`
Rid []string `protobuf:"bytes,2,rep,name=rid,proto3" json:"rid,omitempty"`
UserId *uint64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3,oneof" json:"user_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ListAttachmentRequest) Reset() {
*x = ListAttachmentRequest{}
mi := &file_attachment_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ListAttachmentRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListAttachmentRequest) ProtoMessage() {}
func (x *ListAttachmentRequest) ProtoReflect() protoreflect.Message {
mi := &file_attachment_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListAttachmentRequest.ProtoReflect.Descriptor instead.
func (*ListAttachmentRequest) Descriptor() ([]byte, []int) {
return file_attachment_proto_rawDescGZIP(), []int{2}
}
func (x *ListAttachmentRequest) GetId() []uint64 {
if x != nil {
return x.Id
}
return nil
}
func (x *ListAttachmentRequest) GetRid() []string {
if x != nil {
return x.Rid
}
return nil
}
func (x *ListAttachmentRequest) GetUserId() uint64 {
if x != nil && x.UserId != nil {
return *x.UserId
}
return 0
}
type ListAttachmentResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Attachments [][]byte `protobuf:"bytes,1,rep,name=attachments,proto3" json:"attachments,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ListAttachmentResponse) Reset() {
*x = ListAttachmentResponse{}
mi := &file_attachment_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ListAttachmentResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListAttachmentResponse) ProtoMessage() {}
func (x *ListAttachmentResponse) ProtoReflect() protoreflect.Message {
mi := &file_attachment_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListAttachmentResponse.ProtoReflect.Descriptor instead.
func (*ListAttachmentResponse) Descriptor() ([]byte, []int) {
return file_attachment_proto_rawDescGZIP(), []int{3}
}
func (x *ListAttachmentResponse) GetAttachments() [][]byte {
if x != nil {
return x.Attachments
}
return nil
}
type UpdateVisibilityRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id []uint64 `protobuf:"varint,1,rep,packed,name=id,proto3" json:"id,omitempty"`
Rid []string `protobuf:"bytes,2,rep,name=rid,proto3" json:"rid,omitempty"`
IsIndexable bool `protobuf:"varint,3,opt,name=is_indexable,json=isIndexable,proto3" json:"is_indexable,omitempty"`
UserId *uint64 `protobuf:"varint,4,opt,name=user_id,json=userId,proto3,oneof" json:"user_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UpdateVisibilityRequest) Reset() {
*x = UpdateVisibilityRequest{}
mi := &file_attachment_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UpdateVisibilityRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UpdateVisibilityRequest) ProtoMessage() {}
func (x *UpdateVisibilityRequest) ProtoReflect() protoreflect.Message {
mi := &file_attachment_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UpdateVisibilityRequest.ProtoReflect.Descriptor instead.
func (*UpdateVisibilityRequest) Descriptor() ([]byte, []int) {
return file_attachment_proto_rawDescGZIP(), []int{4}
}
func (x *UpdateVisibilityRequest) GetId() []uint64 {
if x != nil {
return x.Id
}
return nil
}
func (x *UpdateVisibilityRequest) GetRid() []string {
if x != nil {
return x.Rid
}
return nil
}
func (x *UpdateVisibilityRequest) GetIsIndexable() bool {
if x != nil {
return x.IsIndexable
}
return false
}
func (x *UpdateVisibilityRequest) GetUserId() uint64 {
if x != nil && x.UserId != nil {
return *x.UserId
}
return 0
}
type UpdateVisibilityResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Count int32 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UpdateVisibilityResponse) Reset() {
*x = UpdateVisibilityResponse{}
mi := &file_attachment_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UpdateVisibilityResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UpdateVisibilityResponse) ProtoMessage() {}
func (x *UpdateVisibilityResponse) ProtoReflect() protoreflect.Message {
mi := &file_attachment_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UpdateVisibilityResponse.ProtoReflect.Descriptor instead.
func (*UpdateVisibilityResponse) Descriptor() ([]byte, []int) {
return file_attachment_proto_rawDescGZIP(), []int{5}
}
func (x *UpdateVisibilityResponse) GetCount() int32 {
if x != nil {
return x.Count
}
return 0
}
type UpdateUsageRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id []uint64 `protobuf:"varint,1,rep,packed,name=id,proto3" json:"id,omitempty"`
Rid []string `protobuf:"bytes,2,rep,name=rid,proto3" json:"rid,omitempty"`
Delta int64 `protobuf:"varint,3,opt,name=delta,proto3" json:"delta,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UpdateUsageRequest) Reset() {
*x = UpdateUsageRequest{}
mi := &file_attachment_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UpdateUsageRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UpdateUsageRequest) ProtoMessage() {}
func (x *UpdateUsageRequest) ProtoReflect() protoreflect.Message {
mi := &file_attachment_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UpdateUsageRequest.ProtoReflect.Descriptor instead.
func (*UpdateUsageRequest) Descriptor() ([]byte, []int) {
return file_attachment_proto_rawDescGZIP(), []int{6}
}
func (x *UpdateUsageRequest) GetId() []uint64 {
if x != nil {
return x.Id
}
return nil
}
func (x *UpdateUsageRequest) GetRid() []string {
if x != nil {
return x.Rid
}
return nil
}
func (x *UpdateUsageRequest) GetDelta() int64 {
if x != nil {
return x.Delta
}
return 0
}
type UpdateUsageResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Count int32 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UpdateUsageResponse) Reset() {
*x = UpdateUsageResponse{}
mi := &file_attachment_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UpdateUsageResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UpdateUsageResponse) ProtoMessage() {}
func (x *UpdateUsageResponse) ProtoReflect() protoreflect.Message {
mi := &file_attachment_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UpdateUsageResponse.ProtoReflect.Descriptor instead.
func (*UpdateUsageResponse) Descriptor() ([]byte, []int) {
return file_attachment_proto_rawDescGZIP(), []int{7}
}
func (x *UpdateUsageResponse) GetCount() int32 {
if x != nil {
return x.Count
}
return 0
}
type DeleteAttachmentRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id []uint64 `protobuf:"varint,1,rep,packed,name=id,proto3" json:"id,omitempty"`
Rid []string `protobuf:"bytes,2,rep,name=rid,proto3" json:"rid,omitempty"`
UserId *uint64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3,oneof" json:"user_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *DeleteAttachmentRequest) Reset() {
*x = DeleteAttachmentRequest{}
mi := &file_attachment_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *DeleteAttachmentRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeleteAttachmentRequest) ProtoMessage() {}
func (x *DeleteAttachmentRequest) ProtoReflect() protoreflect.Message {
mi := &file_attachment_proto_msgTypes[8]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeleteAttachmentRequest.ProtoReflect.Descriptor instead.
func (*DeleteAttachmentRequest) Descriptor() ([]byte, []int) {
return file_attachment_proto_rawDescGZIP(), []int{8}
}
func (x *DeleteAttachmentRequest) GetId() []uint64 {
if x != nil {
return x.Id
}
return nil
}
func (x *DeleteAttachmentRequest) GetRid() []string {
if x != nil {
return x.Rid
}
return nil
}
func (x *DeleteAttachmentRequest) GetUserId() uint64 {
if x != nil && x.UserId != nil {
return *x.UserId
}
return 0
}
type DeleteAttachmentResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Count int32 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *DeleteAttachmentResponse) Reset() {
*x = DeleteAttachmentResponse{}
mi := &file_attachment_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *DeleteAttachmentResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeleteAttachmentResponse) ProtoMessage() {}
func (x *DeleteAttachmentResponse) ProtoReflect() protoreflect.Message {
mi := &file_attachment_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeleteAttachmentResponse.ProtoReflect.Descriptor instead.
func (*DeleteAttachmentResponse) Descriptor() ([]byte, []int) {
return file_attachment_proto_rawDescGZIP(), []int{9}
}
func (x *DeleteAttachmentResponse) GetCount() int32 {
if x != nil {
return x.Count
}
return 0
}
var File_attachment_proto protoreflect.FileDescriptor
const file_attachment_proto_rawDesc = "" +
"\n" +
"\x10attachment.proto\x12\x05proto\"{\n" +
"\x14GetAttachmentRequest\x12\x13\n" +
"\x02id\x18\x01 \x01(\x04H\x00R\x02id\x88\x01\x01\x12\x15\n" +
"\x03rid\x18\x02 \x01(\tH\x01R\x03rid\x88\x01\x01\x12\x1c\n" +
"\auser_id\x18\x03 \x01(\x04H\x02R\x06userId\x88\x01\x01B\x05\n" +
"\x03_idB\x06\n" +
"\x04_ridB\n" +
"\n" +
"\b_user_id\"K\n" +
"\x15GetAttachmentResponse\x12#\n" +
"\n" +
"attachment\x18\x01 \x01(\fH\x00R\n" +
"attachment\x88\x01\x01B\r\n" +
"\v_attachment\"c\n" +
"\x15ListAttachmentRequest\x12\x0e\n" +
"\x02id\x18\x01 \x03(\x04R\x02id\x12\x10\n" +
"\x03rid\x18\x02 \x03(\tR\x03rid\x12\x1c\n" +
"\auser_id\x18\x03 \x01(\x04H\x00R\x06userId\x88\x01\x01B\n" +
"\n" +
"\b_user_id\":\n" +
"\x16ListAttachmentResponse\x12 \n" +
"\vattachments\x18\x01 \x03(\fR\vattachments\"\x88\x01\n" +
"\x17UpdateVisibilityRequest\x12\x0e\n" +
"\x02id\x18\x01 \x03(\x04R\x02id\x12\x10\n" +
"\x03rid\x18\x02 \x03(\tR\x03rid\x12!\n" +
"\fis_indexable\x18\x03 \x01(\bR\visIndexable\x12\x1c\n" +
"\auser_id\x18\x04 \x01(\x04H\x00R\x06userId\x88\x01\x01B\n" +
"\n" +
"\b_user_id\"0\n" +
"\x18UpdateVisibilityResponse\x12\x14\n" +
"\x05count\x18\x01 \x01(\x05R\x05count\"L\n" +
"\x12UpdateUsageRequest\x12\x0e\n" +
"\x02id\x18\x01 \x03(\x04R\x02id\x12\x10\n" +
"\x03rid\x18\x02 \x03(\tR\x03rid\x12\x14\n" +
"\x05delta\x18\x03 \x01(\x03R\x05delta\"+\n" +
"\x13UpdateUsageResponse\x12\x14\n" +
"\x05count\x18\x01 \x01(\x05R\x05count\"e\n" +
"\x17DeleteAttachmentRequest\x12\x0e\n" +
"\x02id\x18\x01 \x03(\x04R\x02id\x12\x10\n" +
"\x03rid\x18\x02 \x03(\tR\x03rid\x12\x1c\n" +
"\auser_id\x18\x03 \x01(\x04H\x00R\x06userId\x88\x01\x01B\n" +
"\n" +
"\b_user_id\"0\n" +
"\x18DeleteAttachmentResponse\x12\x14\n" +
"\x05count\x18\x01 \x01(\x05R\x05count2\xa8\x03\n" +
"\x11AttachmentService\x12L\n" +
"\rGetAttachment\x12\x1b.proto.GetAttachmentRequest\x1a\x1c.proto.GetAttachmentResponse\"\x00\x12O\n" +
"\x0eListAttachment\x12\x1c.proto.ListAttachmentRequest\x1a\x1d.proto.ListAttachmentResponse\"\x00\x12U\n" +
"\x10UpdateVisibility\x12\x1e.proto.UpdateVisibilityRequest\x1a\x1f.proto.UpdateVisibilityResponse\"\x00\x12F\n" +
"\vUpdateUsage\x12\x19.proto.UpdateUsageRequest\x1a\x1a.proto.UpdateUsageResponse\"\x00\x12U\n" +
"\x10DeleteAttachment\x12\x1e.proto.DeleteAttachmentRequest\x1a\x1f.proto.DeleteAttachmentResponse\"\x00B\tZ\a.;protob\x06proto3"
var (
file_attachment_proto_rawDescOnce sync.Once
file_attachment_proto_rawDescData []byte
)
func file_attachment_proto_rawDescGZIP() []byte {
file_attachment_proto_rawDescOnce.Do(func() {
file_attachment_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_attachment_proto_rawDesc), len(file_attachment_proto_rawDesc)))
})
return file_attachment_proto_rawDescData
}
var file_attachment_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_attachment_proto_goTypes = []any{
(*GetAttachmentRequest)(nil), // 0: proto.GetAttachmentRequest
(*GetAttachmentResponse)(nil), // 1: proto.GetAttachmentResponse
(*ListAttachmentRequest)(nil), // 2: proto.ListAttachmentRequest
(*ListAttachmentResponse)(nil), // 3: proto.ListAttachmentResponse
(*UpdateVisibilityRequest)(nil), // 4: proto.UpdateVisibilityRequest
(*UpdateVisibilityResponse)(nil), // 5: proto.UpdateVisibilityResponse
(*UpdateUsageRequest)(nil), // 6: proto.UpdateUsageRequest
(*UpdateUsageResponse)(nil), // 7: proto.UpdateUsageResponse
(*DeleteAttachmentRequest)(nil), // 8: proto.DeleteAttachmentRequest
(*DeleteAttachmentResponse)(nil), // 9: proto.DeleteAttachmentResponse
}
var file_attachment_proto_depIdxs = []int32{
0, // 0: proto.AttachmentService.GetAttachment:input_type -> proto.GetAttachmentRequest
2, // 1: proto.AttachmentService.ListAttachment:input_type -> proto.ListAttachmentRequest
4, // 2: proto.AttachmentService.UpdateVisibility:input_type -> proto.UpdateVisibilityRequest
6, // 3: proto.AttachmentService.UpdateUsage:input_type -> proto.UpdateUsageRequest
8, // 4: proto.AttachmentService.DeleteAttachment:input_type -> proto.DeleteAttachmentRequest
1, // 5: proto.AttachmentService.GetAttachment:output_type -> proto.GetAttachmentResponse
3, // 6: proto.AttachmentService.ListAttachment:output_type -> proto.ListAttachmentResponse
5, // 7: proto.AttachmentService.UpdateVisibility:output_type -> proto.UpdateVisibilityResponse
7, // 8: proto.AttachmentService.UpdateUsage:output_type -> proto.UpdateUsageResponse
9, // 9: proto.AttachmentService.DeleteAttachment:output_type -> proto.DeleteAttachmentResponse
5, // [5:10] is the sub-list for method output_type
0, // [0:5] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_attachment_proto_init() }
func file_attachment_proto_init() {
if File_attachment_proto != nil {
return
}
file_attachment_proto_msgTypes[0].OneofWrappers = []any{}
file_attachment_proto_msgTypes[1].OneofWrappers = []any{}
file_attachment_proto_msgTypes[2].OneofWrappers = []any{}
file_attachment_proto_msgTypes[4].OneofWrappers = []any{}
file_attachment_proto_msgTypes[8].OneofWrappers = []any{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_attachment_proto_rawDesc), len(file_attachment_proto_rawDesc)),
NumEnums: 0,
NumMessages: 10,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_attachment_proto_goTypes,
DependencyIndexes: file_attachment_proto_depIdxs,
MessageInfos: file_attachment_proto_msgTypes,
}.Build()
File_attachment_proto = out.File
file_attachment_proto_goTypes = nil
file_attachment_proto_depIdxs = nil
}

View File

@@ -0,0 +1,64 @@
syntax = "proto3";
option go_package = ".;proto";
package proto;
service AttachmentService {
rpc GetAttachment(GetAttachmentRequest) returns (GetAttachmentResponse) {}
rpc ListAttachment(ListAttachmentRequest) returns (ListAttachmentResponse) {}
rpc UpdateVisibility(UpdateVisibilityRequest) returns (UpdateVisibilityResponse) {}
rpc UpdateUsage(UpdateUsageRequest) returns (UpdateUsageResponse) {}
rpc DeleteAttachment(DeleteAttachmentRequest) returns (DeleteAttachmentResponse) {}
}
message GetAttachmentRequest {
optional uint64 id = 1;
optional string rid = 2;
optional uint64 user_id = 3;
}
message GetAttachmentResponse {
optional bytes attachment = 1;
}
message ListAttachmentRequest {
repeated uint64 id = 1;
repeated string rid = 2;
optional uint64 user_id = 3;
}
message ListAttachmentResponse {
repeated bytes attachments = 1;
}
message UpdateVisibilityRequest {
repeated uint64 id = 1;
repeated string rid = 2;
bool is_indexable = 3;
optional uint64 user_id = 4;
}
message UpdateVisibilityResponse {
int32 count = 1;
}
message UpdateUsageRequest {
repeated uint64 id = 1;
repeated string rid = 2;
int64 delta = 3;
}
message UpdateUsageResponse {
int32 count = 1;
}
message DeleteAttachmentRequest {
repeated uint64 id = 1;
repeated string rid = 2;
optional uint64 user_id = 3;
}
message DeleteAttachmentResponse {
int32 count = 1;
}

View File

@@ -0,0 +1,273 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.3
// source: attachment.proto
package proto
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
AttachmentService_GetAttachment_FullMethodName = "/proto.AttachmentService/GetAttachment"
AttachmentService_ListAttachment_FullMethodName = "/proto.AttachmentService/ListAttachment"
AttachmentService_UpdateVisibility_FullMethodName = "/proto.AttachmentService/UpdateVisibility"
AttachmentService_UpdateUsage_FullMethodName = "/proto.AttachmentService/UpdateUsage"
AttachmentService_DeleteAttachment_FullMethodName = "/proto.AttachmentService/DeleteAttachment"
)
// AttachmentServiceClient is the client API for AttachmentService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type AttachmentServiceClient interface {
GetAttachment(ctx context.Context, in *GetAttachmentRequest, opts ...grpc.CallOption) (*GetAttachmentResponse, error)
ListAttachment(ctx context.Context, in *ListAttachmentRequest, opts ...grpc.CallOption) (*ListAttachmentResponse, error)
UpdateVisibility(ctx context.Context, in *UpdateVisibilityRequest, opts ...grpc.CallOption) (*UpdateVisibilityResponse, error)
UpdateUsage(ctx context.Context, in *UpdateUsageRequest, opts ...grpc.CallOption) (*UpdateUsageResponse, error)
DeleteAttachment(ctx context.Context, in *DeleteAttachmentRequest, opts ...grpc.CallOption) (*DeleteAttachmentResponse, error)
}
type attachmentServiceClient struct {
cc grpc.ClientConnInterface
}
func NewAttachmentServiceClient(cc grpc.ClientConnInterface) AttachmentServiceClient {
return &attachmentServiceClient{cc}
}
func (c *attachmentServiceClient) GetAttachment(ctx context.Context, in *GetAttachmentRequest, opts ...grpc.CallOption) (*GetAttachmentResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetAttachmentResponse)
err := c.cc.Invoke(ctx, AttachmentService_GetAttachment_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *attachmentServiceClient) ListAttachment(ctx context.Context, in *ListAttachmentRequest, opts ...grpc.CallOption) (*ListAttachmentResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListAttachmentResponse)
err := c.cc.Invoke(ctx, AttachmentService_ListAttachment_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *attachmentServiceClient) UpdateVisibility(ctx context.Context, in *UpdateVisibilityRequest, opts ...grpc.CallOption) (*UpdateVisibilityResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(UpdateVisibilityResponse)
err := c.cc.Invoke(ctx, AttachmentService_UpdateVisibility_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *attachmentServiceClient) UpdateUsage(ctx context.Context, in *UpdateUsageRequest, opts ...grpc.CallOption) (*UpdateUsageResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(UpdateUsageResponse)
err := c.cc.Invoke(ctx, AttachmentService_UpdateUsage_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *attachmentServiceClient) DeleteAttachment(ctx context.Context, in *DeleteAttachmentRequest, opts ...grpc.CallOption) (*DeleteAttachmentResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(DeleteAttachmentResponse)
err := c.cc.Invoke(ctx, AttachmentService_DeleteAttachment_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// AttachmentServiceServer is the server API for AttachmentService service.
// All implementations must embed UnimplementedAttachmentServiceServer
// for forward compatibility.
type AttachmentServiceServer interface {
GetAttachment(context.Context, *GetAttachmentRequest) (*GetAttachmentResponse, error)
ListAttachment(context.Context, *ListAttachmentRequest) (*ListAttachmentResponse, error)
UpdateVisibility(context.Context, *UpdateVisibilityRequest) (*UpdateVisibilityResponse, error)
UpdateUsage(context.Context, *UpdateUsageRequest) (*UpdateUsageResponse, error)
DeleteAttachment(context.Context, *DeleteAttachmentRequest) (*DeleteAttachmentResponse, error)
mustEmbedUnimplementedAttachmentServiceServer()
}
// UnimplementedAttachmentServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedAttachmentServiceServer struct{}
func (UnimplementedAttachmentServiceServer) GetAttachment(context.Context, *GetAttachmentRequest) (*GetAttachmentResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetAttachment not implemented")
}
func (UnimplementedAttachmentServiceServer) ListAttachment(context.Context, *ListAttachmentRequest) (*ListAttachmentResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListAttachment not implemented")
}
func (UnimplementedAttachmentServiceServer) UpdateVisibility(context.Context, *UpdateVisibilityRequest) (*UpdateVisibilityResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateVisibility not implemented")
}
func (UnimplementedAttachmentServiceServer) UpdateUsage(context.Context, *UpdateUsageRequest) (*UpdateUsageResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateUsage not implemented")
}
func (UnimplementedAttachmentServiceServer) DeleteAttachment(context.Context, *DeleteAttachmentRequest) (*DeleteAttachmentResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteAttachment not implemented")
}
func (UnimplementedAttachmentServiceServer) mustEmbedUnimplementedAttachmentServiceServer() {}
func (UnimplementedAttachmentServiceServer) testEmbeddedByValue() {}
// UnsafeAttachmentServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to AttachmentServiceServer will
// result in compilation errors.
type UnsafeAttachmentServiceServer interface {
mustEmbedUnimplementedAttachmentServiceServer()
}
func RegisterAttachmentServiceServer(s grpc.ServiceRegistrar, srv AttachmentServiceServer) {
// If the following call pancis, it indicates UnimplementedAttachmentServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&AttachmentService_ServiceDesc, srv)
}
func _AttachmentService_GetAttachment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetAttachmentRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AttachmentServiceServer).GetAttachment(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: AttachmentService_GetAttachment_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AttachmentServiceServer).GetAttachment(ctx, req.(*GetAttachmentRequest))
}
return interceptor(ctx, in, info, handler)
}
func _AttachmentService_ListAttachment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListAttachmentRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AttachmentServiceServer).ListAttachment(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: AttachmentService_ListAttachment_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AttachmentServiceServer).ListAttachment(ctx, req.(*ListAttachmentRequest))
}
return interceptor(ctx, in, info, handler)
}
func _AttachmentService_UpdateVisibility_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateVisibilityRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AttachmentServiceServer).UpdateVisibility(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: AttachmentService_UpdateVisibility_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AttachmentServiceServer).UpdateVisibility(ctx, req.(*UpdateVisibilityRequest))
}
return interceptor(ctx, in, info, handler)
}
func _AttachmentService_UpdateUsage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateUsageRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AttachmentServiceServer).UpdateUsage(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: AttachmentService_UpdateUsage_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AttachmentServiceServer).UpdateUsage(ctx, req.(*UpdateUsageRequest))
}
return interceptor(ctx, in, info, handler)
}
func _AttachmentService_DeleteAttachment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeleteAttachmentRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AttachmentServiceServer).DeleteAttachment(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: AttachmentService_DeleteAttachment_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AttachmentServiceServer).DeleteAttachment(ctx, req.(*DeleteAttachmentRequest))
}
return interceptor(ctx, in, info, handler)
}
// AttachmentService_ServiceDesc is the grpc.ServiceDesc for AttachmentService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var AttachmentService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "proto.AttachmentService",
HandlerType: (*AttachmentServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetAttachment",
Handler: _AttachmentService_GetAttachment_Handler,
},
{
MethodName: "ListAttachment",
Handler: _AttachmentService_ListAttachment_Handler,
},
{
MethodName: "UpdateVisibility",
Handler: _AttachmentService_UpdateVisibility_Handler,
},
{
MethodName: "UpdateUsage",
Handler: _AttachmentService_UpdateUsage_Handler,
},
{
MethodName: "DeleteAttachment",
Handler: _AttachmentService_DeleteAttachment_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "attachment.proto",
}

View File

@@ -1,43 +1,38 @@
id = "paperclip01"
bind = "0.0.0.0:8443"
grpc_bind = "0.0.0.0:7443"
bind = "0.0.0.0:8004"
grpc_bind = "0.0.0.0:7004"
nexus_addr = "localhost:7001"
preferred_destination = 1
[workers]
files_deletion = 4
files_analyze = 4
[debug]
database = true
database = false
print_routes = false
[dealer]
addr = "127.0.0.1:7442"
[security]
cookie_domain = "localhost"
cookie_samesite = "Lax"
access_token_duration = 300
refresh_token_duration = 2592000
[performance]
file_chunk_size = 26214400
[database]
dsn = "host=localhost user=postgres password=password dbname=hy_paperclip port=5432 sslmode=disable"
prefix = "paperclip_"
[pools.aliases]
"p.avatar" = "avatar"
"p.banner" = "avatar"
"i.attachment" = "interactive"
"m.attachment" = "messaging"
"sticker" = "sticker"
[destinations.temporary]
[[destinations]]
type = "local"
path = "uploads"
[destinations.permanent]
[[destinations]]
type = "local"
path = "uploads/permanent"
access_baseurl = "http://192.168.50.133:8004"
image_proxy_baseurl = "https://io.sn.solsynth.dev"
[traffic]
maximum_size = 20971520
minimum_size = 1048576
[security]
internal_public_key = "keys/internal_public_key.pem"
[payment]
discount = 52428800