Mythus commited on
Commit
3502a78
·
1 Parent(s): 4f44725

Upload 46 files

Browse files
.github/buildkit.toml ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [worker.oci]
2
+ max-parallelism = 4
.github/dependabot.yml ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: "gomod" # See documentation for possible values
9
+ directory: "/" # Location of package manifests
10
+ schedule:
11
+ interval: "daily"
12
+ assignees:
13
+ - "divyam234"
.github/workflows/build.yml ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Build App
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ tags:
7
+ - '*'
8
+ env:
9
+ DOCKER_BUILDKIT: 1
10
+
11
+ jobs:
12
+ build_image:
13
+ name: Build Image
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - name: Checkout
17
+ uses: actions/checkout@v3
18
+ with:
19
+ fetch-depth: 0
20
+ - name: Install buildx
21
+ uses: docker/setup-buildx-action@v2
22
+ with:
23
+ config: .github/buildkit.toml
24
+
25
+ - name: Login to Docker
26
+ uses: docker/login-action@v2
27
+ with:
28
+ registry: ghcr.io
29
+ username: ${{ github.actor }}
30
+ password: ${{ secrets.GITHUB_TOKEN }}
31
+
32
+ - name: Image Name
33
+ id: imagename
34
+ run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}/server" >> $GITHUB_OUTPUT
35
+
36
+ - name: Build Image
37
+ uses: docker/build-push-action@v3
38
+ with:
39
+ context: ./
40
+ platforms: linux/amd64,linux/arm64,linux/arm/v7
41
+ pull: true
42
+ push: true
43
+ tags: ${{ steps.imagename.outputs.name }}:latest
.github/workflows/goreleaser.yml ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Goreleaser
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "*"
7
+
8
+ permissions:
9
+ contents: write
10
+
11
+ jobs:
12
+ goreleaser:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - name: Checkout
16
+ uses: actions/checkout@v3
17
+ with:
18
+ fetch-depth: 0
19
+ - name: Set up Go
20
+ uses: actions/setup-go@v4
21
+ with:
22
+ go-version: 1.21
23
+ - name: Run GoReleaser
24
+ uses: goreleaser/goreleaser-action@v4
25
+ with:
26
+ distribution: goreleaser
27
+ version: latest
28
+ args: release --clean
29
+ env:
30
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
.gitignore ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # If you prefer the allow list template instead of the deny list, see community template:
2
+ # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3
+ #
4
+ # Binaries for programs and plugins
5
+ *.exe
6
+ *.exe~
7
+ *.dll
8
+ *.so
9
+ *.dylib
10
+
11
+ # Test binary, built with `go test -c`
12
+ *.test
13
+
14
+ # Output of the go coverage tool, specifically when used with LiteIDE
15
+ *.out
16
+
17
+ # Dependency directories (remove the comment below to include it)
18
+ # vendor/
19
+
20
+ # Go workspace file
21
+ go.work
22
+
23
+ sessions
24
+ certs
25
+ sslcerts
26
+
27
+ *.env
28
+ *.env.example
29
+ *.env.local
30
+ *.env.staging
.goreleaser.yaml ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ project_name: teldrive
2
+ env:
3
+ - GO111MODULE=on
4
+ builds:
5
+ - env:
6
+ - CGO_ENABLED=0
7
+ flags:
8
+ - -trimpath
9
+ ldflags:
10
+ - -s -w -extldflags "-static"
11
+ - -X {{ .ModulePath }}/pkg/consts.Version={{ .Version }}
12
+ - -X {{ .ModulePath }}/pkg/consts.Commit={{ .ShortCommit }}
13
+ - -X {{ .ModulePath }}/pkg/consts.CommitDate={{ .CommitDate }}
14
+ mod_timestamp: "{{ .CommitTimestamp }}"
15
+ goos:
16
+ - linux
17
+ - windows
18
+ - darwin
19
+ goarch:
20
+ - amd64
21
+ - arm
22
+ - arm64
23
+ checksum:
24
+ name_template: "{{ .ProjectName }}_checksums.txt"
25
+ archives:
26
+ - name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
27
+ format_overrides:
28
+ - goos: windows
29
+ format: zip
30
+ files:
31
+ - README*.md
32
+ - LICENSE
.vscode/launch.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ // Use IntelliSense to learn about possible attributes.
3
+ // Hover to view descriptions of existing attributes.
4
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "name": "Launch Package",
9
+ "type": "go",
10
+ "request": "launch",
11
+ "mode": "auto",
12
+ "program": "main.go"
13
+ }
14
+ ]
15
+ }
Dockerfile ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:alpine as builder
2
+
3
+ ARG TARGETPLATFORM
4
+ ARG BUILDPLATFORM
5
+ ARG TARGETOS
6
+ ARG TARGETARCH
7
+
8
+
9
+ RUN apk update && apk add --no-cache git ca-certificates tzdata && update-ca-certificates
10
+
11
+ WORKDIR /app
12
+
13
+ # use modules
14
+ COPY go.mod .
15
+
16
+ ENV GO111MODULE=on
17
+ RUN go mod download && go mod verify
18
+
19
+ COPY . .
20
+
21
+ RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build \
22
+ -ldflags='-w -s -extldflags "-static"' -a \
23
+ -o /app/teldrive .
24
+
25
+
26
+ FROM --platform=${TARGETPLATFORM:-linux/amd64} scratch
27
+
28
+ WORKDIR /app
29
+
30
+ COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
31
+ COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
32
+
33
+ COPY --from=builder /app/teldrive /app/teldrive
34
+
35
+ EXPOSE 8080
36
+
37
+ ENTRYPOINT ["/app/teldrive"]
LICENSE ADDED
@@ -0,0 +1,674 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ GNU GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+ Preamble
9
+
10
+ The GNU General Public License is a free, copyleft license for
11
+ software and other kinds of works.
12
+
13
+ The licenses for most software and other practical works are designed
14
+ to take away your freedom to share and change the works. By contrast,
15
+ the GNU General Public License is intended to guarantee your freedom to
16
+ share and change all versions of a program--to make sure it remains free
17
+ software for all its users. We, the Free Software Foundation, use the
18
+ GNU General Public License for most of our software; it applies also to
19
+ any other work released this way by its authors. You can apply it to
20
+ your programs, too.
21
+
22
+ When we speak of free software, we are referring to freedom, not
23
+ price. Our General Public Licenses are designed to make sure that you
24
+ have the freedom to distribute copies of free software (and charge for
25
+ them if you wish), that you receive source code or can get it if you
26
+ want it, that you can change the software or use pieces of it in new
27
+ free programs, and that you know you can do these things.
28
+
29
+ To protect your rights, we need to prevent others from denying you
30
+ these rights or asking you to surrender the rights. Therefore, you have
31
+ certain responsibilities if you distribute copies of the software, or if
32
+ you modify it: responsibilities to respect the freedom of others.
33
+
34
+ For example, if you distribute copies of such a program, whether
35
+ gratis or for a fee, you must pass on to the recipients the same
36
+ freedoms that you received. You must make sure that they, too, receive
37
+ or can get the source code. And you must show them these terms so they
38
+ know their rights.
39
+
40
+ Developers that use the GNU GPL protect your rights with two steps:
41
+ (1) assert copyright on the software, and (2) offer you this License
42
+ giving you legal permission to copy, distribute and/or modify it.
43
+
44
+ For the developers' and authors' protection, the GPL clearly explains
45
+ that there is no warranty for this free software. For both users' and
46
+ authors' sake, the GPL requires that modified versions be marked as
47
+ changed, so that their problems will not be attributed erroneously to
48
+ authors of previous versions.
49
+
50
+ Some devices are designed to deny users access to install or run
51
+ modified versions of the software inside them, although the manufacturer
52
+ can do so. This is fundamentally incompatible with the aim of
53
+ protecting users' freedom to change the software. The systematic
54
+ pattern of such abuse occurs in the area of products for individuals to
55
+ use, which is precisely where it is most unacceptable. Therefore, we
56
+ have designed this version of the GPL to prohibit the practice for those
57
+ products. If such problems arise substantially in other domains, we
58
+ stand ready to extend this provision to those domains in future versions
59
+ of the GPL, as needed to protect the freedom of users.
60
+
61
+ Finally, every program is threatened constantly by software patents.
62
+ States should not allow patents to restrict development and use of
63
+ software on general-purpose computers, but in those that do, we wish to
64
+ avoid the special danger that patents applied to a free program could
65
+ make it effectively proprietary. To prevent this, the GPL assures that
66
+ patents cannot be used to render the program non-free.
67
+
68
+ The precise terms and conditions for copying, distribution and
69
+ modification follow.
70
+
71
+ TERMS AND CONDITIONS
72
+
73
+ 0. Definitions.
74
+
75
+ "This License" refers to version 3 of the GNU General Public License.
76
+
77
+ "Copyright" also means copyright-like laws that apply to other kinds of
78
+ works, such as semiconductor masks.
79
+
80
+ "The Program" refers to any copyrightable work licensed under this
81
+ License. Each licensee is addressed as "you". "Licensees" and
82
+ "recipients" may be individuals or organizations.
83
+
84
+ To "modify" a work means to copy from or adapt all or part of the work
85
+ in a fashion requiring copyright permission, other than the making of an
86
+ exact copy. The resulting work is called a "modified version" of the
87
+ earlier work or a work "based on" the earlier work.
88
+
89
+ A "covered work" means either the unmodified Program or a work based
90
+ on the Program.
91
+
92
+ To "propagate" a work means to do anything with it that, without
93
+ permission, would make you directly or secondarily liable for
94
+ infringement under applicable copyright law, except executing it on a
95
+ computer or modifying a private copy. Propagation includes copying,
96
+ distribution (with or without modification), making available to the
97
+ public, and in some countries other activities as well.
98
+
99
+ To "convey" a work means any kind of propagation that enables other
100
+ parties to make or receive copies. Mere interaction with a user through
101
+ a computer network, with no transfer of a copy, is not conveying.
102
+
103
+ An interactive user interface displays "Appropriate Legal Notices"
104
+ to the extent that it includes a convenient and prominently visible
105
+ feature that (1) displays an appropriate copyright notice, and (2)
106
+ tells the user that there is no warranty for the work (except to the
107
+ extent that warranties are provided), that licensees may convey the
108
+ work under this License, and how to view a copy of this License. If
109
+ the interface presents a list of user commands or options, such as a
110
+ menu, a prominent item in the list meets this criterion.
111
+
112
+ 1. Source Code.
113
+
114
+ The "source code" for a work means the preferred form of the work
115
+ for making modifications to it. "Object code" means any non-source
116
+ form of a work.
117
+
118
+ A "Standard Interface" means an interface that either is an official
119
+ standard defined by a recognized standards body, or, in the case of
120
+ interfaces specified for a particular programming language, one that
121
+ is widely used among developers working in that language.
122
+
123
+ The "System Libraries" of an executable work include anything, other
124
+ than the work as a whole, that (a) is included in the normal form of
125
+ packaging a Major Component, but which is not part of that Major
126
+ Component, and (b) serves only to enable use of the work with that
127
+ Major Component, or to implement a Standard Interface for which an
128
+ implementation is available to the public in source code form. A
129
+ "Major Component", in this context, means a major essential component
130
+ (kernel, window system, and so on) of the specific operating system
131
+ (if any) on which the executable work runs, or a compiler used to
132
+ produce the work, or an object code interpreter used to run it.
133
+
134
+ The "Corresponding Source" for a work in object code form means all
135
+ the source code needed to generate, install, and (for an executable
136
+ work) run the object code and to modify the work, including scripts to
137
+ control those activities. However, it does not include the work's
138
+ System Libraries, or general-purpose tools or generally available free
139
+ programs which are used unmodified in performing those activities but
140
+ which are not part of the work. For example, Corresponding Source
141
+ includes interface definition files associated with source files for
142
+ the work, and the source code for shared libraries and dynamically
143
+ linked subprograms that the work is specifically designed to require,
144
+ such as by intimate data communication or control flow between those
145
+ subprograms and other parts of the work.
146
+
147
+ The Corresponding Source need not include anything that users
148
+ can regenerate automatically from other parts of the Corresponding
149
+ Source.
150
+
151
+ The Corresponding Source for a work in source code form is that
152
+ same work.
153
+
154
+ 2. Basic Permissions.
155
+
156
+ All rights granted under this License are granted for the term of
157
+ copyright on the Program, and are irrevocable provided the stated
158
+ conditions are met. This License explicitly affirms your unlimited
159
+ permission to run the unmodified Program. The output from running a
160
+ covered work is covered by this License only if the output, given its
161
+ content, constitutes a covered work. This License acknowledges your
162
+ rights of fair use or other equivalent, as provided by copyright law.
163
+
164
+ You may make, run and propagate covered works that you do not
165
+ convey, without conditions so long as your license otherwise remains
166
+ in force. You may convey covered works to others for the sole purpose
167
+ of having them make modifications exclusively for you, or provide you
168
+ with facilities for running those works, provided that you comply with
169
+ the terms of this License in conveying all material for which you do
170
+ not control copyright. Those thus making or running the covered works
171
+ for you must do so exclusively on your behalf, under your direction
172
+ and control, on terms that prohibit them from making any copies of
173
+ your copyrighted material outside their relationship with you.
174
+
175
+ Conveying under any other circumstances is permitted solely under
176
+ the conditions stated below. Sublicensing is not allowed; section 10
177
+ makes it unnecessary.
178
+
179
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180
+
181
+ No covered work shall be deemed part of an effective technological
182
+ measure under any applicable law fulfilling obligations under article
183
+ 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184
+ similar laws prohibiting or restricting circumvention of such
185
+ measures.
186
+
187
+ When you convey a covered work, you waive any legal power to forbid
188
+ circumvention of technological measures to the extent such circumvention
189
+ is effected by exercising rights under this License with respect to
190
+ the covered work, and you disclaim any intention to limit operation or
191
+ modification of the work as a means of enforcing, against the work's
192
+ users, your or third parties' legal rights to forbid circumvention of
193
+ technological measures.
194
+
195
+ 4. Conveying Verbatim Copies.
196
+
197
+ You may convey verbatim copies of the Program's source code as you
198
+ receive it, in any medium, provided that you conspicuously and
199
+ appropriately publish on each copy an appropriate copyright notice;
200
+ keep intact all notices stating that this License and any
201
+ non-permissive terms added in accord with section 7 apply to the code;
202
+ keep intact all notices of the absence of any warranty; and give all
203
+ recipients a copy of this License along with the Program.
204
+
205
+ You may charge any price or no price for each copy that you convey,
206
+ and you may offer support or warranty protection for a fee.
207
+
208
+ 5. Conveying Modified Source Versions.
209
+
210
+ You may convey a work based on the Program, or the modifications to
211
+ produce it from the Program, in the form of source code under the
212
+ terms of section 4, provided that you also meet all of these conditions:
213
+
214
+ a) The work must carry prominent notices stating that you modified
215
+ it, and giving a relevant date.
216
+
217
+ b) The work must carry prominent notices stating that it is
218
+ released under this License and any conditions added under section
219
+ 7. This requirement modifies the requirement in section 4 to
220
+ "keep intact all notices".
221
+
222
+ c) You must license the entire work, as a whole, under this
223
+ License to anyone who comes into possession of a copy. This
224
+ License will therefore apply, along with any applicable section 7
225
+ additional terms, to the whole of the work, and all its parts,
226
+ regardless of how they are packaged. This License gives no
227
+ permission to license the work in any other way, but it does not
228
+ invalidate such permission if you have separately received it.
229
+
230
+ d) If the work has interactive user interfaces, each must display
231
+ Appropriate Legal Notices; however, if the Program has interactive
232
+ interfaces that do not display Appropriate Legal Notices, your
233
+ work need not make them do so.
234
+
235
+ A compilation of a covered work with other separate and independent
236
+ works, which are not by their nature extensions of the covered work,
237
+ and which are not combined with it such as to form a larger program,
238
+ in or on a volume of a storage or distribution medium, is called an
239
+ "aggregate" if the compilation and its resulting copyright are not
240
+ used to limit the access or legal rights of the compilation's users
241
+ beyond what the individual works permit. Inclusion of a covered work
242
+ in an aggregate does not cause this License to apply to the other
243
+ parts of the aggregate.
244
+
245
+ 6. Conveying Non-Source Forms.
246
+
247
+ You may convey a covered work in object code form under the terms
248
+ of sections 4 and 5, provided that you also convey the
249
+ machine-readable Corresponding Source under the terms of this License,
250
+ in one of these ways:
251
+
252
+ a) Convey the object code in, or embodied in, a physical product
253
+ (including a physical distribution medium), accompanied by the
254
+ Corresponding Source fixed on a durable physical medium
255
+ customarily used for software interchange.
256
+
257
+ b) Convey the object code in, or embodied in, a physical product
258
+ (including a physical distribution medium), accompanied by a
259
+ written offer, valid for at least three years and valid for as
260
+ long as you offer spare parts or customer support for that product
261
+ model, to give anyone who possesses the object code either (1) a
262
+ copy of the Corresponding Source for all the software in the
263
+ product that is covered by this License, on a durable physical
264
+ medium customarily used for software interchange, for a price no
265
+ more than your reasonable cost of physically performing this
266
+ conveying of source, or (2) access to copy the
267
+ Corresponding Source from a network server at no charge.
268
+
269
+ c) Convey individual copies of the object code with a copy of the
270
+ written offer to provide the Corresponding Source. This
271
+ alternative is allowed only occasionally and noncommercially, and
272
+ only if you received the object code with such an offer, in accord
273
+ with subsection 6b.
274
+
275
+ d) Convey the object code by offering access from a designated
276
+ place (gratis or for a charge), and offer equivalent access to the
277
+ Corresponding Source in the same way through the same place at no
278
+ further charge. You need not require recipients to copy the
279
+ Corresponding Source along with the object code. If the place to
280
+ copy the object code is a network server, the Corresponding Source
281
+ may be on a different server (operated by you or a third party)
282
+ that supports equivalent copying facilities, provided you maintain
283
+ clear directions next to the object code saying where to find the
284
+ Corresponding Source. Regardless of what server hosts the
285
+ Corresponding Source, you remain obligated to ensure that it is
286
+ available for as long as needed to satisfy these requirements.
287
+
288
+ e) Convey the object code using peer-to-peer transmission, provided
289
+ you inform other peers where the object code and Corresponding
290
+ Source of the work are being offered to the general public at no
291
+ charge under subsection 6d.
292
+
293
+ A separable portion of the object code, whose source code is excluded
294
+ from the Corresponding Source as a System Library, need not be
295
+ included in conveying the object code work.
296
+
297
+ A "User Product" is either (1) a "consumer product", which means any
298
+ tangible personal property which is normally used for personal, family,
299
+ or household purposes, or (2) anything designed or sold for incorporation
300
+ into a dwelling. In determining whether a product is a consumer product,
301
+ doubtful cases shall be resolved in favor of coverage. For a particular
302
+ product received by a particular user, "normally used" refers to a
303
+ typical or common use of that class of product, regardless of the status
304
+ of the particular user or of the way in which the particular user
305
+ actually uses, or expects or is expected to use, the product. A product
306
+ is a consumer product regardless of whether the product has substantial
307
+ commercial, industrial or non-consumer uses, unless such uses represent
308
+ the only significant mode of use of the product.
309
+
310
+ "Installation Information" for a User Product means any methods,
311
+ procedures, authorization keys, or other information required to install
312
+ and execute modified versions of a covered work in that User Product from
313
+ a modified version of its Corresponding Source. The information must
314
+ suffice to ensure that the continued functioning of the modified object
315
+ code is in no case prevented or interfered with solely because
316
+ modification has been made.
317
+
318
+ If you convey an object code work under this section in, or with, or
319
+ specifically for use in, a User Product, and the conveying occurs as
320
+ part of a transaction in which the right of possession and use of the
321
+ User Product is transferred to the recipient in perpetuity or for a
322
+ fixed term (regardless of how the transaction is characterized), the
323
+ Corresponding Source conveyed under this section must be accompanied
324
+ by the Installation Information. But this requirement does not apply
325
+ if neither you nor any third party retains the ability to install
326
+ modified object code on the User Product (for example, the work has
327
+ been installed in ROM).
328
+
329
+ The requirement to provide Installation Information does not include a
330
+ requirement to continue to provide support service, warranty, or updates
331
+ for a work that has been modified or installed by the recipient, or for
332
+ the User Product in which it has been modified or installed. Access to a
333
+ network may be denied when the modification itself materially and
334
+ adversely affects the operation of the network or violates the rules and
335
+ protocols for communication across the network.
336
+
337
+ Corresponding Source conveyed, and Installation Information provided,
338
+ in accord with this section must be in a format that is publicly
339
+ documented (and with an implementation available to the public in
340
+ source code form), and must require no special password or key for
341
+ unpacking, reading or copying.
342
+
343
+ 7. Additional Terms.
344
+
345
+ "Additional permissions" are terms that supplement the terms of this
346
+ License by making exceptions from one or more of its conditions.
347
+ Additional permissions that are applicable to the entire Program shall
348
+ be treated as though they were included in this License, to the extent
349
+ that they are valid under applicable law. If additional permissions
350
+ apply only to part of the Program, that part may be used separately
351
+ under those permissions, but the entire Program remains governed by
352
+ this License without regard to the additional permissions.
353
+
354
+ When you convey a copy of a covered work, you may at your option
355
+ remove any additional permissions from that copy, or from any part of
356
+ it. (Additional permissions may be written to require their own
357
+ removal in certain cases when you modify the work.) You may place
358
+ additional permissions on material, added by you to a covered work,
359
+ for which you have or can give appropriate copyright permission.
360
+
361
+ Notwithstanding any other provision of this License, for material you
362
+ add to a covered work, you may (if authorized by the copyright holders of
363
+ that material) supplement the terms of this License with terms:
364
+
365
+ a) Disclaiming warranty or limiting liability differently from the
366
+ terms of sections 15 and 16 of this License; or
367
+
368
+ b) Requiring preservation of specified reasonable legal notices or
369
+ author attributions in that material or in the Appropriate Legal
370
+ Notices displayed by works containing it; or
371
+
372
+ c) Prohibiting misrepresentation of the origin of that material, or
373
+ requiring that modified versions of such material be marked in
374
+ reasonable ways as different from the original version; or
375
+
376
+ d) Limiting the use for publicity purposes of names of licensors or
377
+ authors of the material; or
378
+
379
+ e) Declining to grant rights under trademark law for use of some
380
+ trade names, trademarks, or service marks; or
381
+
382
+ f) Requiring indemnification of licensors and authors of that
383
+ material by anyone who conveys the material (or modified versions of
384
+ it) with contractual assumptions of liability to the recipient, for
385
+ any liability that these contractual assumptions directly impose on
386
+ those licensors and authors.
387
+
388
+ All other non-permissive additional terms are considered "further
389
+ restrictions" within the meaning of section 10. If the Program as you
390
+ received it, or any part of it, contains a notice stating that it is
391
+ governed by this License along with a term that is a further
392
+ restriction, you may remove that term. If a license document contains
393
+ a further restriction but permits relicensing or conveying under this
394
+ License, you may add to a covered work material governed by the terms
395
+ of that license document, provided that the further restriction does
396
+ not survive such relicensing or conveying.
397
+
398
+ If you add terms to a covered work in accord with this section, you
399
+ must place, in the relevant source files, a statement of the
400
+ additional terms that apply to those files, or a notice indicating
401
+ where to find the applicable terms.
402
+
403
+ Additional terms, permissive or non-permissive, may be stated in the
404
+ form of a separately written license, or stated as exceptions;
405
+ the above requirements apply either way.
406
+
407
+ 8. Termination.
408
+
409
+ You may not propagate or modify a covered work except as expressly
410
+ provided under this License. Any attempt otherwise to propagate or
411
+ modify it is void, and will automatically terminate your rights under
412
+ this License (including any patent licenses granted under the third
413
+ paragraph of section 11).
414
+
415
+ However, if you cease all violation of this License, then your
416
+ license from a particular copyright holder is reinstated (a)
417
+ provisionally, unless and until the copyright holder explicitly and
418
+ finally terminates your license, and (b) permanently, if the copyright
419
+ holder fails to notify you of the violation by some reasonable means
420
+ prior to 60 days after the cessation.
421
+
422
+ Moreover, your license from a particular copyright holder is
423
+ reinstated permanently if the copyright holder notifies you of the
424
+ violation by some reasonable means, this is the first time you have
425
+ received notice of violation of this License (for any work) from that
426
+ copyright holder, and you cure the violation prior to 30 days after
427
+ your receipt of the notice.
428
+
429
+ Termination of your rights under this section does not terminate the
430
+ licenses of parties who have received copies or rights from you under
431
+ this License. If your rights have been terminated and not permanently
432
+ reinstated, you do not qualify to receive new licenses for the same
433
+ material under section 10.
434
+
435
+ 9. Acceptance Not Required for Having Copies.
436
+
437
+ You are not required to accept this License in order to receive or
438
+ run a copy of the Program. Ancillary propagation of a covered work
439
+ occurring solely as a consequence of using peer-to-peer transmission
440
+ to receive a copy likewise does not require acceptance. However,
441
+ nothing other than this License grants you permission to propagate or
442
+ modify any covered work. These actions infringe copyright if you do
443
+ not accept this License. Therefore, by modifying or propagating a
444
+ covered work, you indicate your acceptance of this License to do so.
445
+
446
+ 10. Automatic Licensing of Downstream Recipients.
447
+
448
+ Each time you convey a covered work, the recipient automatically
449
+ receives a license from the original licensors, to run, modify and
450
+ propagate that work, subject to this License. You are not responsible
451
+ for enforcing compliance by third parties with this License.
452
+
453
+ An "entity transaction" is a transaction transferring control of an
454
+ organization, or substantially all assets of one, or subdividing an
455
+ organization, or merging organizations. If propagation of a covered
456
+ work results from an entity transaction, each party to that
457
+ transaction who receives a copy of the work also receives whatever
458
+ licenses to the work the party's predecessor in interest had or could
459
+ give under the previous paragraph, plus a right to possession of the
460
+ Corresponding Source of the work from the predecessor in interest, if
461
+ the predecessor has it or can get it with reasonable efforts.
462
+
463
+ You may not impose any further restrictions on the exercise of the
464
+ rights granted or affirmed under this License. For example, you may
465
+ not impose a license fee, royalty, or other charge for exercise of
466
+ rights granted under this License, and you may not initiate litigation
467
+ (including a cross-claim or counterclaim in a lawsuit) alleging that
468
+ any patent claim is infringed by making, using, selling, offering for
469
+ sale, or importing the Program or any portion of it.
470
+
471
+ 11. Patents.
472
+
473
+ A "contributor" is a copyright holder who authorizes use under this
474
+ License of the Program or a work on which the Program is based. The
475
+ work thus licensed is called the contributor's "contributor version".
476
+
477
+ A contributor's "essential patent claims" are all patent claims
478
+ owned or controlled by the contributor, whether already acquired or
479
+ hereafter acquired, that would be infringed by some manner, permitted
480
+ by this License, of making, using, or selling its contributor version,
481
+ but do not include claims that would be infringed only as a
482
+ consequence of further modification of the contributor version. For
483
+ purposes of this definition, "control" includes the right to grant
484
+ patent sublicenses in a manner consistent with the requirements of
485
+ this License.
486
+
487
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
488
+ patent license under the contributor's essential patent claims, to
489
+ make, use, sell, offer for sale, import and otherwise run, modify and
490
+ propagate the contents of its contributor version.
491
+
492
+ In the following three paragraphs, a "patent license" is any express
493
+ agreement or commitment, however denominated, not to enforce a patent
494
+ (such as an express permission to practice a patent or covenant not to
495
+ sue for patent infringement). To "grant" such a patent license to a
496
+ party means to make such an agreement or commitment not to enforce a
497
+ patent against the party.
498
+
499
+ If you convey a covered work, knowingly relying on a patent license,
500
+ and the Corresponding Source of the work is not available for anyone
501
+ to copy, free of charge and under the terms of this License, through a
502
+ publicly available network server or other readily accessible means,
503
+ then you must either (1) cause the Corresponding Source to be so
504
+ available, or (2) arrange to deprive yourself of the benefit of the
505
+ patent license for this particular work, or (3) arrange, in a manner
506
+ consistent with the requirements of this License, to extend the patent
507
+ license to downstream recipients. "Knowingly relying" means you have
508
+ actual knowledge that, but for the patent license, your conveying the
509
+ covered work in a country, or your recipient's use of the covered work
510
+ in a country, would infringe one or more identifiable patents in that
511
+ country that you have reason to believe are valid.
512
+
513
+ If, pursuant to or in connection with a single transaction or
514
+ arrangement, you convey, or propagate by procuring conveyance of, a
515
+ covered work, and grant a patent license to some of the parties
516
+ receiving the covered work authorizing them to use, propagate, modify
517
+ or convey a specific copy of the covered work, then the patent license
518
+ you grant is automatically extended to all recipients of the covered
519
+ work and works based on it.
520
+
521
+ A patent license is "discriminatory" if it does not include within
522
+ the scope of its coverage, prohibits the exercise of, or is
523
+ conditioned on the non-exercise of one or more of the rights that are
524
+ specifically granted under this License. You may not convey a covered
525
+ work if you are a party to an arrangement with a third party that is
526
+ in the business of distributing software, under which you make payment
527
+ to the third party based on the extent of your activity of conveying
528
+ the work, and under which the third party grants, to any of the
529
+ parties who would receive the covered work from you, a discriminatory
530
+ patent license (a) in connection with copies of the covered work
531
+ conveyed by you (or copies made from those copies), or (b) primarily
532
+ for and in connection with specific products or compilations that
533
+ contain the covered work, unless you entered into that arrangement,
534
+ or that patent license was granted, prior to 28 March 2007.
535
+
536
+ Nothing in this License shall be construed as excluding or limiting
537
+ any implied license or other defenses to infringement that may
538
+ otherwise be available to you under applicable patent law.
539
+
540
+ 12. No Surrender of Others' Freedom.
541
+
542
+ If conditions are imposed on you (whether by court order, agreement or
543
+ otherwise) that contradict the conditions of this License, they do not
544
+ excuse you from the conditions of this License. If you cannot convey a
545
+ covered work so as to satisfy simultaneously your obligations under this
546
+ License and any other pertinent obligations, then as a consequence you may
547
+ not convey it at all. For example, if you agree to terms that obligate you
548
+ to collect a royalty for further conveying from those to whom you convey
549
+ the Program, the only way you could satisfy both those terms and this
550
+ License would be to refrain entirely from conveying the Program.
551
+
552
+ 13. Use with the GNU Affero General Public License.
553
+
554
+ Notwithstanding any other provision of this License, you have
555
+ permission to link or combine any covered work with a work licensed
556
+ under version 3 of the GNU Affero General Public License into a single
557
+ combined work, and to convey the resulting work. The terms of this
558
+ License will continue to apply to the part which is the covered work,
559
+ but the special requirements of the GNU Affero General Public License,
560
+ section 13, concerning interaction through a network will apply to the
561
+ combination as such.
562
+
563
+ 14. Revised Versions of this License.
564
+
565
+ The Free Software Foundation may publish revised and/or new versions of
566
+ the GNU General Public License from time to time. Such new versions will
567
+ be similar in spirit to the present version, but may differ in detail to
568
+ address new problems or concerns.
569
+
570
+ Each version is given a distinguishing version number. If the
571
+ Program specifies that a certain numbered version of the GNU General
572
+ Public License "or any later version" applies to it, you have the
573
+ option of following the terms and conditions either of that numbered
574
+ version or of any later version published by the Free Software
575
+ Foundation. If the Program does not specify a version number of the
576
+ GNU General Public License, you may choose any version ever published
577
+ by the Free Software Foundation.
578
+
579
+ If the Program specifies that a proxy can decide which future
580
+ versions of the GNU General Public License can be used, that proxy's
581
+ public statement of acceptance of a version permanently authorizes you
582
+ to choose that version for the Program.
583
+
584
+ Later license versions may give you additional or different
585
+ permissions. However, no additional obligations are imposed on any
586
+ author or copyright holder as a result of your choosing to follow a
587
+ later version.
588
+
589
+ 15. Disclaimer of Warranty.
590
+
591
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592
+ APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593
+ HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594
+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596
+ PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597
+ IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598
+ ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599
+
600
+ 16. Limitation of Liability.
601
+
602
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604
+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605
+ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606
+ USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607
+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608
+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609
+ EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610
+ SUCH DAMAGES.
611
+
612
+ 17. Interpretation of Sections 15 and 16.
613
+
614
+ If the disclaimer of warranty and limitation of liability provided
615
+ above cannot be given local legal effect according to their terms,
616
+ reviewing courts shall apply local law that most closely approximates
617
+ an absolute waiver of all civil liability in connection with the
618
+ Program, unless a warranty or assumption of liability accompanies a
619
+ copy of the Program in return for a fee.
620
+
621
+ END OF TERMS AND CONDITIONS
622
+
623
+ How to Apply These Terms to Your New Programs
624
+
625
+ If you develop a new program, and you want it to be of the greatest
626
+ possible use to the public, the best way to achieve this is to make it
627
+ free software which everyone can redistribute and change under these terms.
628
+
629
+ To do so, attach the following notices to the program. It is safest
630
+ to attach them to the start of each source file to most effectively
631
+ state the exclusion of warranty; and each file should have at least
632
+ the "copyright" line and a pointer to where the full notice is found.
633
+
634
+ <one line to give the program's name and a brief idea of what it does.>
635
+ Copyright (C) <year> <name of author>
636
+
637
+ This program is free software: you can redistribute it and/or modify
638
+ it under the terms of the GNU General Public License as published by
639
+ the Free Software Foundation, either version 3 of the License, or
640
+ (at your option) any later version.
641
+
642
+ This program is distributed in the hope that it will be useful,
643
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
644
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645
+ GNU General Public License for more details.
646
+
647
+ You should have received a copy of the GNU General Public License
648
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
649
+
650
+ Also add information on how to contact you by electronic and paper mail.
651
+
652
+ If the program does terminal interaction, make it output a short
653
+ notice like this when it starts in an interactive mode:
654
+
655
+ <program> Copyright (C) <year> <name of author>
656
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657
+ This is free software, and you are welcome to redistribute it
658
+ under certain conditions; type `show c' for details.
659
+
660
+ The hypothetical commands `show w' and `show c' should show the appropriate
661
+ parts of the General Public License. Of course, your program's commands
662
+ might be different; for a GUI interface, you would use an "about box".
663
+
664
+ You should also get your employer (if you work as a programmer) or school,
665
+ if any, to sign a "copyright disclaimer" for the program, if necessary.
666
+ For more information on this, and how to apply and follow the GNU GPL, see
667
+ <https://www.gnu.org/licenses/>.
668
+
669
+ The GNU General Public License does not permit incorporating your program
670
+ into proprietary programs. If your program is a subroutine library, you
671
+ may consider it more useful to permit linking proprietary applications with
672
+ the library. If this is what you want to do, use the GNU Lesser General
673
+ Public License instead of this License. But first, please read
674
+ <https://www.gnu.org/licenses/why-not-lgpl.html>.
cache/bigcache.go ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package cache
2
+
3
+ import (
4
+ "bytes"
5
+ "context"
6
+ "encoding/gob"
7
+ "errors"
8
+
9
+ "github.com/allegro/bigcache/v3"
10
+ )
11
+
12
+ type bigCache struct {
13
+ cache *bigcache.BigCache
14
+ }
15
+
16
+ func newBigCache(cacheConfig *cacheConfig) (*bigCache, error) {
17
+ cache, err := bigcache.New(context.Background(), bigcache.Config{
18
+ Shards: 16,
19
+ LifeWindow: cacheConfig.ttl,
20
+ CleanWindow: cacheConfig.cleanFreq,
21
+ MaxEntriesInWindow: 1000 * 10 * 60,
22
+ MaxEntrySize: 500,
23
+ Verbose: false,
24
+ HardMaxCacheSize: cacheConfig.size,
25
+ StatsEnabled: true,
26
+ })
27
+ if err != nil {
28
+ return nil, err
29
+ }
30
+ return &bigCache{
31
+ cache: cache,
32
+ }, nil
33
+ }
34
+
35
+ // Set inserts the key/value pair into the cache.
36
+ // Only the exported fields of the given struct will be
37
+ // serialized and stored
38
+ func (c *bigCache) Set(key, value interface{}) error {
39
+ keyString, ok := key.(string)
40
+ if !ok {
41
+ return errors.New("a cache key must be a string")
42
+ }
43
+
44
+ valueBytes, err := serializeGOB(value)
45
+ if err != nil {
46
+ return err
47
+ }
48
+
49
+ return c.cache.Set(keyString, valueBytes)
50
+ }
51
+
52
+ // Get returns the value correlating to the key in the cache
53
+ func (c *bigCache) Get(key interface{}) (interface{}, error) {
54
+ // Assert the key is of string type
55
+ keyString, ok := key.(string)
56
+ if !ok {
57
+ return nil, errors.New("a cache key must be a string")
58
+ }
59
+
60
+ // Get the value in the byte format it is stored in
61
+ valueBytes, err := c.cache.Get(keyString)
62
+ if err != nil {
63
+ return nil, err
64
+ }
65
+
66
+ // Deserialize the bytes of the value
67
+ value, err := deserializeGOB(valueBytes)
68
+ if err != nil {
69
+ return nil, err
70
+ }
71
+
72
+ return value, nil
73
+ }
74
+
75
+ func serializeGOB(value interface{}) ([]byte, error) {
76
+ buf := bytes.Buffer{}
77
+ enc := gob.NewEncoder(&buf)
78
+ gob.Register(value)
79
+
80
+ err := enc.Encode(&value)
81
+ if err != nil {
82
+ return nil, err
83
+ }
84
+
85
+ return buf.Bytes(), nil
86
+ }
87
+
88
+ func deserializeGOB(valueBytes []byte) (interface{}, error) {
89
+ var value interface{}
90
+ buf := bytes.NewBuffer(valueBytes)
91
+ dec := gob.NewDecoder(buf)
92
+
93
+ err := dec.Decode(&value)
94
+ if err != nil {
95
+ return nil, err
96
+ }
97
+
98
+ return value, nil
99
+ }
cache/cache.go ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package cache
2
+
3
+ import (
4
+ "time"
5
+ )
6
+
7
+ type cacheConfig struct {
8
+ size int // Size in MB
9
+ ttl time.Duration
10
+ cleanFreq time.Duration
11
+ }
12
+
13
+ // Interface to wrap any caching implementation
14
+ type Cache interface {
15
+ Set(key, value interface{}) error // Only exported fields in struct will be stored
16
+ Get(key interface{}) (interface{}, error)
17
+ }
18
+
19
+ // New builds a new default cache. You may pass options to modify the default values
20
+ func New(opts ...Option) (Cache, error) {
21
+ cacheConfig := &cacheConfig{
22
+ size: 1,
23
+ ttl: 60 * time.Second,
24
+ cleanFreq: 30 * time.Second,
25
+ }
26
+
27
+ for _, opt := range opts {
28
+ opt.apply(cacheConfig)
29
+ }
30
+
31
+ cache, err := newBigCache(cacheConfig)
32
+ if err != nil {
33
+ return nil, err
34
+ }
35
+ return cache, nil
36
+ }
37
+
38
+ type Option interface {
39
+ apply(cacheConfig *cacheConfig)
40
+ }
41
+
42
+ type optionFunc func(*cacheConfig)
43
+
44
+ func (opt optionFunc) apply(cacheConfig *cacheConfig) {
45
+ opt(cacheConfig)
46
+ }
47
+
48
+ // WithSizeInMB sets the size of the cache in MBs
49
+ // The minimum size of the cache is 1 MB
50
+ // If a size of 0 or less is passed the cache will have unlimited size
51
+ func WithSizeInMB(size int) Option {
52
+ return optionFunc(func(cacheConfig *cacheConfig) {
53
+ cacheConfig.size = size
54
+ })
55
+ }
56
+
57
+ // WithTTL will cause the cache to expire any item that lives longer
58
+ // than the given ttl
59
+ func WithTTL(ttl time.Duration) Option {
60
+ return optionFunc(func(cacheConfig *cacheConfig) {
61
+ cacheConfig.ttl = ttl
62
+ })
63
+ }
64
+
65
+ // WithCleanFrequency sets how often the cache will clean out expired items
66
+ // The lowest the frequency may be is 1 second
67
+ // If the time is 0 then no cleaning will happen and items will never be removed
68
+ func WithCleanFrequency(cleanFreq time.Duration) Option {
69
+ return optionFunc(func(cacheConfig *cacheConfig) {
70
+ cacheConfig.cleanFreq = cleanFreq
71
+ })
72
+ }
cache/cachutil.go ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package cache
2
+
3
+ import (
4
+ "reflect"
5
+ "time"
6
+ )
7
+
8
+ var globalCache Cache
9
+
10
+ func CacheInit() {
11
+
12
+ var err error
13
+ globalCache, err = New(
14
+ WithSizeInMB(10),
15
+ WithTTL(12*time.Hour),
16
+ WithCleanFrequency(24*time.Hour),
17
+ )
18
+ if err != nil {
19
+ panic("Failed to initialize global cache: " + err.Error())
20
+ }
21
+ }
22
+
23
+ func GetCache() Cache {
24
+ return globalCache
25
+ }
26
+
27
+ func CachedFunction(fn interface{}, key string) func(...interface{}) (interface{}, error) {
28
+ return func(args ...interface{}) (interface{}, error) {
29
+
30
+ // Check if the result is already cached
31
+ if cachedResult, err := globalCache.Get(key); err == nil {
32
+ return cachedResult, nil
33
+ }
34
+
35
+ // If not cached, call the original function to get the result
36
+ f := reflect.ValueOf(fn)
37
+ if len(args) == 0 {
38
+ args = nil // Ensure nil is passed when there are no arguments.
39
+ }
40
+ result := f.Call(getArgs(args))
41
+
42
+ // Check if the function returned an error as the last return value
43
+ if err, ok := result[len(result)-1].Interface().(error); ok && err != nil {
44
+ return nil, err
45
+ }
46
+
47
+ // Extract the result from the function call
48
+ finalResult := result[0].Interface()
49
+
50
+ // Cache the result with a default TTL (time-to-live)
51
+ globalCache.Set(key, finalResult)
52
+
53
+ return finalResult, nil
54
+ }
55
+ }
56
+
57
+ func getArgs(args []interface{}) []reflect.Value {
58
+ var values []reflect.Value
59
+ for _, arg := range args {
60
+ values = append(values, reflect.ValueOf(arg))
61
+ }
62
+ return values
63
+ }
database/database.go ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package database
2
+
3
+ import (
4
+ "log"
5
+ "os"
6
+ "path/filepath"
7
+ "time"
8
+
9
+ "github.com/divyam234/teldrive/utils"
10
+ "github.com/pressly/goose/v3"
11
+ "gorm.io/driver/postgres"
12
+ "gorm.io/gorm"
13
+ "gorm.io/gorm/logger"
14
+ "gorm.io/gorm/schema"
15
+ )
16
+
17
+ var DB *gorm.DB
18
+
19
+ func InitDB() {
20
+
21
+ var err error
22
+
23
+ newLogger := logger.New(
24
+ log.New(os.Stdout, "\r\n", log.LstdFlags),
25
+ logger.Config{
26
+ SlowThreshold: time.Second,
27
+ LogLevel: logger.Silent,
28
+ IgnoreRecordNotFoundError: true,
29
+ ParameterizedQueries: true,
30
+ Colorful: false,
31
+ },
32
+ )
33
+
34
+ DB, err = gorm.Open(postgres.Open(utils.GetConfig().DatabaseUrl), &gorm.Config{
35
+ NamingStrategy: schema.NamingStrategy{
36
+ TablePrefix: "teldrive.",
37
+ SingularTable: false,
38
+ },
39
+ PrepareStmt: false,
40
+ NowFunc: func() time.Time {
41
+ return time.Now().UTC()
42
+ },
43
+ Logger: newLogger,
44
+ })
45
+ if err != nil {
46
+ panic(err)
47
+ }
48
+
49
+ sqlDB, err := DB.DB()
50
+ if err != nil {
51
+ panic(err)
52
+ }
53
+ sqlDB.SetMaxIdleConns(10)
54
+ sqlDB.SetMaxOpenConns(100)
55
+
56
+ sqlDB.SetConnMaxLifetime(time.Hour)
57
+ go func() {
58
+ DB.Exec(`create collation if not exists numeric (provider = icu, locale = 'en@colnumeric=yes');`)
59
+ if utils.GetConfig().RunMigrations {
60
+ migrate()
61
+ }
62
+ }()
63
+
64
+ }
65
+
66
+ func migrate() {
67
+
68
+ config := utils.GetConfig()
69
+
70
+ if err := goose.SetDialect("postgres"); err != nil {
71
+ panic(err)
72
+ }
73
+ db, _ := DB.DB()
74
+ if err := goose.Up(db, filepath.Join(config.ExecDir, "database", "migrations")); err != nil {
75
+ panic(err)
76
+ }
77
+ }
database/migrations/20230817172319_init.sql ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- +goose Up
2
+ create extension if not exists pgcrypto;
3
+
4
+ create extension if not exists btree_gin;
5
+
6
+ create schema if not exists teldrive;
7
+
8
+ create collation if not exists numeric (provider = icu, locale = 'en@colnumeric=yes');
9
+
10
+ -- +goose StatementBegin
11
+ create or replace
12
+ function teldrive.generate_uid(size int) returns text language plpgsql as $$
13
+ declare characters text := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
14
+
15
+ bytes bytea := gen_random_bytes(size);
16
+
17
+ l int := length(characters);
18
+
19
+ i int := 0;
20
+
21
+ output text := '';
22
+
23
+ begin while i < size loop output := output || substr(characters,
24
+ get_byte(bytes,
25
+ i) % l + 1,
26
+ 1);
27
+
28
+ i := i + 1;
29
+ end loop;
30
+
31
+ return output;
32
+ end;
33
+
34
+ $$;
35
+ -- +goose StatementEnd
36
+
37
+ -- +goose StatementBegin
38
+ create or replace
39
+ function teldrive.get_tsvector(t text) returns tsvector language plpgsql immutable as $$
40
+ declare res tsvector := to_tsvector(regexp_replace(t,
41
+ '[^A-Za-z0-9 ]',
42
+ ' ',
43
+ 'g'));
44
+
45
+ begin return res;
46
+ end;
47
+
48
+ $$;
49
+ -- +goose StatementEnd
50
+
51
+ -- +goose StatementBegin
52
+ create or replace
53
+ function teldrive.get_tsquery(t text) returns tsquery language plpgsql immutable as $$
54
+ declare res tsquery = concat(
55
+ plainto_tsquery(regexp_replace(t,
56
+ '[^A-Za-z0-9 ]',
57
+ ' ',
58
+ 'g')),
59
+ ':*'
60
+ )::tsquery;
61
+
62
+ begin return res;
63
+ end;
64
+
65
+ $$;
66
+ -- +goose StatementEnd
database/migrations/20230817172325_tables.sql ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- +goose Up
2
+ create table teldrive.files (
3
+ id text primary key not null default teldrive.generate_uid(16),
4
+ name text not null,
5
+ type text not null,
6
+ mime_type text not null,
7
+ path text null,
8
+ size bigint null,
9
+ starred bool not null,
10
+ depth integer null,
11
+ user_id bigint not null,
12
+ parent_id text null,
13
+ status text default 'active'::text,
14
+ channel_id bigint null,
15
+ parts jsonb null,
16
+ created_at timestamp not null default timezone('utc'::text,
17
+ now()),
18
+ updated_at timestamp not null default timezone('utc'::text,
19
+ now()),
20
+ constraint unique_file unique (name,
21
+ parent_id,user_id)
22
+ );
23
+
24
+ create table teldrive.uploads (
25
+ id text not null primary key default teldrive.generate_uid(16),
26
+ upload_id text not null,
27
+ name text not null,
28
+ part_no int4 not null,
29
+ part_id int4 not null,
30
+ total_parts int4 not null,
31
+ channel_id int8 not null,
32
+ size int8 not null,
33
+ created_at timestamp null default timezone('utc'::text,
34
+ now())
35
+ );
36
+
37
+ create table teldrive.users (
38
+ user_id bigint not null primary key,
39
+ name text null,
40
+ user_name text null,
41
+ is_premium bool not null,
42
+ tg_session text not null,
43
+ settings jsonb null,
44
+ created_at timestamptz not null default timezone('utc'::text,
45
+ now()),
46
+ updated_at timestamptz not null default timezone('utc'::text,
47
+ now())
48
+ );
49
+
50
+ create collation if not exists numeric (provider = icu, locale = 'en@colnumeric=yes');
51
+ create index name_search_idx on
52
+ teldrive.files
53
+ using gin (teldrive.get_tsvector(name),
54
+ updated_at);
55
+
56
+ create index name_numeric_idx on
57
+ teldrive.files(name collate numeric nulls first);
58
+
59
+ create index parent_name_numeric_idx on
60
+ teldrive.files (parent_id,
61
+ name collate numeric desc);
62
+
63
+ create index path_idx on
64
+ teldrive.files (path);
65
+
66
+ create index parent_idx on
67
+ teldrive.files (parent_id);
68
+
69
+ create index starred_updated_at_idx on
70
+ teldrive.files (starred,
71
+ updated_at desc);
72
+
73
+ create index status_idx on teldrive.files (status);
74
+
75
+ create index user_id_idx on teldrive.files (user_id);
76
+
77
+ -- +goose Down
78
+ drop table if exists teldrive.files;
79
+ drop table if exists teldrive.uploads;
80
+ drop table if exists teldrive.users;
81
+ drop index if exists teldrive.name_search_idx;
82
+ drop index if exists teldrive.name_numeric_idx;
83
+ drop index if exists teldrive.parent_name_numeric_idx;
84
+ drop index if exists teldrive.path_idx;
85
+ drop index if exists teldrive.parent_idx;
86
+ drop index if exists teldrive.starred_updated_at_idx;
87
+ drop index if exists teldrive.status_idx;
88
+ drop index if exists teldrive.user_id_idx;
database/migrations/20230817172329_functions.sql ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- +goose Up
2
+
3
+ -- +goose StatementBegin
4
+ create procedure teldrive.update_size() language plpgsql as $$
5
+ declare rec record;
6
+
7
+ total_size bigint;
8
+
9
+ begin
10
+
11
+ for rec in
12
+ select
13
+ id
14
+ from
15
+ files
16
+ where
17
+ type = 'folder'
18
+ order by
19
+ depth desc loop total_size := (
20
+ select
21
+ sum(size) as total_size
22
+ from
23
+ teldrive.files
24
+ where
25
+ parent_id = rec.id
26
+ );
27
+
28
+ update
29
+ teldrive.files
30
+ set
31
+ size = total_size
32
+ where
33
+ id = rec.id;
34
+ end loop;
35
+ end;
36
+
37
+ $$;
38
+ -- +goose StatementEnd
39
+
40
+
41
+ -- +goose StatementBegin
42
+ create function teldrive.update_folder(folder_id text,
43
+ new_name text default null,
44
+ new_path text default null)
45
+ returns setof teldrive.files
46
+ language plpgsql
47
+ as $$
48
+ declare folder record;
49
+
50
+ path_items text [];
51
+
52
+ begin
53
+
54
+ if new_path is null then
55
+ select
56
+ *
57
+ into
58
+ folder
59
+ from
60
+ teldrive.files
61
+ where
62
+ id = folder_id;
63
+
64
+ path_items := string_to_array(folder.path,
65
+ '/');
66
+
67
+ path_items [array_length(path_items,
68
+ 1)] := new_name;
69
+
70
+ new_path := array_to_string(path_items,
71
+ '/');
72
+ end if;
73
+
74
+ update
75
+ teldrive.files
76
+ set
77
+ path = new_path,
78
+ name = new_name
79
+ where
80
+ id = folder_id;
81
+
82
+ for folder in
83
+ select
84
+ *
85
+ from
86
+ teldrive.files
87
+ where
88
+ type = 'folder'
89
+ and parent_id = folder_id loop call teldrive.update_folder(
90
+ folder.id,
91
+ folder.name,
92
+ concat(new_path,
93
+ '/',
94
+ folder.name)
95
+ );
96
+ end loop;
97
+
98
+ return query
99
+ select
100
+ *
101
+ from
102
+ teldrive.files
103
+ where
104
+ id = folder_id;
105
+ end;
106
+
107
+ $$
108
+ ;
109
+ -- +goose StatementEnd
110
+
111
+ -- +goose StatementBegin
112
+ create procedure teldrive.delete_files(in file_ids text[],
113
+ in op text default 'bulk')
114
+ language plpgsql
115
+ as $$
116
+ declare
117
+ rec record;
118
+
119
+ begin
120
+ if op = 'bulk' then
121
+ for rec in
122
+ select
123
+ id,
124
+ type
125
+ from
126
+ teldrive.files
127
+ where
128
+ id = any (file_ids)
129
+ loop
130
+ if rec.type = 'folder' then
131
+ call teldrive.delete_files(array [rec.id],
132
+ 'single');
133
+
134
+ delete
135
+ from
136
+ teldrive.files
137
+ where
138
+ id = rec.id;
139
+ else
140
+ update
141
+ teldrive.files
142
+ set
143
+ status = 'pending_deletion'
144
+ where
145
+ id = rec.id;
146
+ end if;
147
+ end loop;
148
+ else
149
+
150
+ for rec in
151
+ select
152
+ id,
153
+ type
154
+ from
155
+ teldrive.files
156
+ where
157
+ parent_id = file_ids[1]
158
+ loop
159
+ if rec.type = 'folder' then
160
+ call teldrive.delete_files(array [rec.id],
161
+ 'single');
162
+
163
+ delete
164
+ from
165
+ teldrive.files
166
+ where
167
+ id = rec.id;
168
+ else
169
+ update
170
+ teldrive.files
171
+ set
172
+ status = 'pending_deletion'
173
+ where
174
+ id = rec.id;
175
+ end if;
176
+ end loop;
177
+ end if;
178
+ end;
179
+
180
+ $$
181
+ ;
182
+ -- +goose StatementEnd
183
+
184
+
185
+ -- +goose Down
186
+ drop procedure if exists teldrive.update_size;
187
+ drop function if exists teldrive.update_folder;
188
+ drop procedure if exists teldrive.delete_files;
database/migrations/20230820014560_create_dirs_from_path.sql ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- +goose Up
2
+ -- +goose StatementBegin
3
+
4
+ CREATE OR REPLACE FUNCTION teldrive.create_directories(
5
+ IN tg_id BIGINT,
6
+ IN long_path TEXT
7
+ ) RETURNS SETOF teldrive.files AS $$
8
+ DECLARE
9
+ path_parts TEXT[];
10
+ current_directory_id TEXT;
11
+ new_directory_id TEXT;
12
+ directory_name TEXT;
13
+ path_so_far TEXT;
14
+ depth_dir integer;
15
+ begin
16
+
17
+ path_parts := string_to_array(regexp_replace(long_path, '^/+', ''), '/');
18
+
19
+ path_so_far := '';
20
+ depth_dir := 0;
21
+
22
+ SELECT id into current_directory_id FROM teldrive.files WHERE parent_id='root';
23
+
24
+
25
+ FOR directory_name IN SELECT unnest(path_parts) LOOP
26
+ path_so_far := CONCAT(path_so_far,'/', directory_name);
27
+ depth_dir := depth_dir +1;
28
+ SELECT id INTO new_directory_id
29
+ FROM teldrive.files
30
+ WHERE parent_id = current_directory_id
31
+ AND "name" = directory_name AND "user_id"=tg_id;
32
+
33
+ IF new_directory_id IS NULL THEN
34
+ INSERT INTO teldrive.files ("name", "type", mime_type, parent_id, "user_id",starred,"depth","path")
35
+ VALUES (directory_name, 'folder', 'drive/folder', current_directory_id, tg_id,false,depth_dir,path_so_far)
36
+ RETURNING id INTO new_directory_id;
37
+ END IF;
38
+
39
+ current_directory_id := new_directory_id;
40
+ END LOOP;
41
+
42
+ RETURN QUERY SELECT * FROM teldrive.files WHERE id = current_directory_id;
43
+ END;
44
+ $$ LANGUAGE plpgsql;
45
+ -- +goose StatementEnd
46
+
47
+ -- +goose Down
48
+ DROP FUNCTION IF EXISTS teldrive.create_directories;
docker-compose.yml ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: "3.8"
2
+
3
+ services:
4
+ server:
5
+ image: ghcr.io/divyam234/teldrive/server:latest
6
+ restart: always
7
+ container_name: server
8
+ volumes:
9
+ - ./sessions:/app/sessions:rw
10
+ - ./database:/app/database
11
+ env_file: .env
12
+ ports:
13
+ - 8080
14
+
15
+ client:
16
+ image: ghcr.io/divyam234/teldrive/client:latest
17
+ restart: always
18
+ container_name: client
19
+ ports:
20
+ - 3000
21
+
22
+ haproxy:
23
+ image: haproxy:latest
24
+ volumes:
25
+ - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg
26
+ # ports:
27
+ # - 80:80
28
+ # - 443:443
29
+ ports:
30
+ - 8000:8000
31
+ depends_on:
32
+ - server
33
+ - client
go.mod ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module github.com/divyam234/teldrive
2
+
3
+ go 1.21
4
+
5
+ require (
6
+ github.com/allegro/bigcache/v3 v3.1.0
7
+ github.com/divyam234/cors v1.4.2
8
+ github.com/gin-gonic/gin v1.9.1
9
+ github.com/go-co-op/gocron v1.31.1
10
+ github.com/go-jose/go-jose/v3 v3.0.0
11
+ github.com/gotd/contrib v0.19.0
12
+ github.com/gotd/td v0.84.0
13
+ github.com/joho/godotenv v1.5.1
14
+ github.com/kelseyhightower/envconfig v1.4.0
15
+ github.com/mitchellh/mapstructure v1.5.0
16
+ github.com/pkg/errors v0.9.1
17
+ github.com/quantumsheep/range-parser v1.1.0
18
+ go.uber.org/zap v1.25.0
19
+ golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb
20
+ golang.org/x/time v0.3.0
21
+ gorm.io/driver/postgres v1.5.2
22
+ gorm.io/gorm v1.25.2
23
+ )
24
+
25
+ require github.com/robfig/cron/v3 v3.0.1 // indirect
26
+
27
+ require (
28
+ github.com/bytedance/sonic v1.9.1 // indirect
29
+ github.com/cenkalti/backoff/v4 v4.2.1
30
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
31
+ github.com/gabriel-vasile/mimetype v1.4.2 // indirect
32
+ github.com/gin-contrib/sse v0.1.0 // indirect
33
+ github.com/go-faster/errors v0.6.1 // indirect
34
+ github.com/go-faster/jx v1.0.1 // indirect
35
+ github.com/go-faster/xor v1.0.0 // indirect
36
+ github.com/go-playground/locales v0.14.1 // indirect
37
+ github.com/go-playground/universal-translator v0.18.1 // indirect
38
+ github.com/go-playground/validator/v10 v10.14.0 // indirect
39
+ github.com/goccy/go-json v0.10.2 // indirect
40
+ github.com/gorilla/websocket v1.5.0
41
+ github.com/gotd/ige v0.2.2 // indirect
42
+ github.com/gotd/neo v0.1.5 // indirect
43
+ github.com/jackc/pgpassfile v1.0.0 // indirect
44
+ github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
45
+ github.com/jackc/pgx/v5 v5.4.3
46
+ github.com/jinzhu/inflection v1.0.0 // indirect
47
+ github.com/jinzhu/now v1.1.5 // indirect
48
+ github.com/json-iterator/go v1.1.12 // indirect
49
+ github.com/klauspost/compress v1.16.7 // indirect
50
+ github.com/klauspost/cpuid/v2 v2.2.4 // indirect
51
+ github.com/leodido/go-urn v1.2.4 // indirect
52
+ github.com/mattn/go-isatty v0.0.19 // indirect
53
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
54
+ github.com/modern-go/reflect2 v1.0.2 // indirect
55
+ github.com/pelletier/go-toml/v2 v2.0.8 // indirect
56
+ github.com/pressly/goose/v3 v3.15.0
57
+ github.com/segmentio/asm v1.2.0 // indirect
58
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
59
+ github.com/ugorji/go/codec v1.2.11 // indirect
60
+ go.opentelemetry.io/otel v1.16.0 // indirect
61
+ go.opentelemetry.io/otel/trace v1.16.0 // indirect
62
+ go.uber.org/atomic v1.11.0 // indirect
63
+ go.uber.org/multierr v1.11.0 // indirect
64
+ golang.org/x/arch v0.3.0 // indirect
65
+ golang.org/x/crypto v0.11.0 // indirect
66
+ golang.org/x/net v0.12.0 // indirect
67
+ golang.org/x/sync v0.3.0 // indirect
68
+ golang.org/x/sys v0.10.0 // indirect
69
+ golang.org/x/text v0.11.0 // indirect
70
+ google.golang.org/protobuf v1.30.0 // indirect
71
+ gopkg.in/yaml.v3 v3.0.1 // indirect
72
+ nhooyr.io/websocket v1.8.7 // indirect
73
+ rsc.io/qr v0.2.0 // indirect
74
+ )
go.sum ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ github.com/allegro/bigcache/v3 v3.1.0 h1:H2Vp8VOvxcrB91o86fUSVJFqeuz8kpyyB02eH3bSzwk=
2
+ github.com/allegro/bigcache/v3 v3.1.0/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I=
3
+ github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
4
+ github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
5
+ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
6
+ github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
7
+ github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
8
+ github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
9
+ github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
10
+ github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
11
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
12
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
13
+ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
14
+ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
15
+ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
16
+ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
17
+ github.com/divyam234/cors v1.4.2 h1:moAxStmYpvG9/SkPz+Wld02iutgo3JcUvrez6Kit/D8=
18
+ github.com/divyam234/cors v1.4.2/go.mod h1:JrxBJAqTU7jtPItodwf2mzxbbZm0Qq0NFkK8jo9UUDk=
19
+ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
20
+ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
21
+ github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
22
+ github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
23
+ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
24
+ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
25
+ github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
26
+ github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
27
+ github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
28
+ github.com/go-co-op/gocron v1.31.1 h1:LZAuBlU0t3SPGUMJGhrJ6VuCc3CsrYzkzicygvVWlfA=
29
+ github.com/go-co-op/gocron v1.31.1/go.mod h1:39f6KNSGVOU1LO/ZOoZfcSxwlsJDQOKSu8erN0SH48Y=
30
+ github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI=
31
+ github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY=
32
+ github.com/go-faster/jx v1.0.1 h1:NhSJEZtqj6KmXf63On7Hg7/sjUX+gotSc/eM6bZCZ00=
33
+ github.com/go-faster/jx v1.0.1/go.mod h1:vKDNikrKoyUmpzaJ0OkIkRQClNHFX/nF3dnTJZb3skg=
34
+ github.com/go-faster/xor v0.3.0/go.mod h1:x5CaDY9UKErKzqfRfFZdfu+OSTfoZny3w5Ak7UxcipQ=
35
+ github.com/go-faster/xor v1.0.0 h1:2o8vTOgErSGHP3/7XwA5ib1FTtUsNtwCoLLBjl31X38=
36
+ github.com/go-faster/xor v1.0.0/go.mod h1:x5CaDY9UKErKzqfRfFZdfu+OSTfoZny3w5Ak7UxcipQ=
37
+ github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
38
+ github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
39
+ github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
40
+ github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
41
+ github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
42
+ github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
43
+ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
44
+ github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
45
+ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
46
+ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
47
+ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
48
+ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
49
+ github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
50
+ github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
51
+ github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
52
+ github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
53
+ github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
54
+ github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
55
+ github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
56
+ github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
57
+ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
58
+ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
59
+ github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
60
+ github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
61
+ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
62
+ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
63
+ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
64
+ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
65
+ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
66
+ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
67
+ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
68
+ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
69
+ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
70
+ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
71
+ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
72
+ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
73
+ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
74
+ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
75
+ github.com/gotd/contrib v0.19.0 h1:O6GvMrRVeFslIHLUcpaHVzcl9/5PcgR2jQTIIeTyds0=
76
+ github.com/gotd/contrib v0.19.0/go.mod h1:LzPxzRF0FvtpBt/WyODWQnPpk0tm/G9z6RHUoPqMakU=
77
+ github.com/gotd/ige v0.2.2 h1:XQ9dJZwBfDnOGSTxKXBGP4gMud3Qku2ekScRjDWWfEk=
78
+ github.com/gotd/ige v0.2.2/go.mod h1:tuCRb+Y5Y3eNTo3ypIfNpQ4MFjrnONiL2jN2AKZXmb0=
79
+ github.com/gotd/neo v0.1.5 h1:oj0iQfMbGClP8xI59x7fE/uHoTJD7NZH9oV1WNuPukQ=
80
+ github.com/gotd/neo v0.1.5/go.mod h1:9A2a4bn9zL6FADufBdt7tZt+WMhvZoc5gWXihOPoiBQ=
81
+ github.com/gotd/td v0.84.0 h1:oWMp5HczCAFSgKWgWFCuYjELBgcRVcRpGLdQ1bP2kpg=
82
+ github.com/gotd/td v0.84.0/go.mod h1:3dQsGL9rxMcS1Z9Na3S7U8e/pLMzbLIT2jM3E5IuUk0=
83
+ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
84
+ github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
85
+ github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
86
+ github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
87
+ github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
88
+ github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
89
+ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
90
+ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
91
+ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
92
+ github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
93
+ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
94
+ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
95
+ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
96
+ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
97
+ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
98
+ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
99
+ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
100
+ github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
101
+ github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
102
+ github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
103
+ github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
104
+ github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
105
+ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
106
+ github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
107
+ github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
108
+ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
109
+ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
110
+ github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
111
+ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
112
+ github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
113
+ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
114
+ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
115
+ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
116
+ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
117
+ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
118
+ github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
119
+ github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
120
+ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
121
+ github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
122
+ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
123
+ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
124
+ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
125
+ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
126
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
127
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
128
+ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
129
+ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
130
+ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
131
+ github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
132
+ github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
133
+ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
134
+ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
135
+ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
136
+ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
137
+ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
138
+ github.com/pressly/goose/v3 v3.15.0 h1:6tY5aDqFknY6VZkorFGgZtWygodZQxfmmEF4rqyJW9k=
139
+ github.com/pressly/goose/v3 v3.15.0/go.mod h1:LlIo3zGccjb/YUgG+Svdb9Er14vefRdlDI7URCDrwYo=
140
+ github.com/quantumsheep/range-parser v1.1.0 h1:k4f1F58f8FF54FBYc9dYBRM+8JkAxFo11gC3IeMH4rU=
141
+ github.com/quantumsheep/range-parser v1.1.0/go.mod h1:acv4Vt2PvpGvRsvGju7Gk2ahKluZJsIUNR69W53J22I=
142
+ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
143
+ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
144
+ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
145
+ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
146
+ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
147
+ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
148
+ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
149
+ github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
150
+ github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
151
+ github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
152
+ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
153
+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
154
+ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
155
+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
156
+ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
157
+ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
158
+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
159
+ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
160
+ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
161
+ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
162
+ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
163
+ github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
164
+ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
165
+ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
166
+ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
167
+ github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
168
+ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
169
+ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
170
+ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
171
+ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
172
+ go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
173
+ go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
174
+ go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
175
+ go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
176
+ go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
177
+ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
178
+ go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
179
+ go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
180
+ go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
181
+ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
182
+ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
183
+ go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c=
184
+ go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk=
185
+ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
186
+ golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
187
+ golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
188
+ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
189
+ golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
190
+ golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
191
+ golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
192
+ golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb h1:mIKbk8weKhSeLH2GmUTrvx8CjkyJmnU1wFmg59CUjFA=
193
+ golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
194
+ golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
195
+ golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
196
+ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
197
+ golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
198
+ golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
199
+ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
200
+ golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
201
+ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
202
+ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
203
+ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
204
+ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
205
+ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
206
+ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
207
+ golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
208
+ golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
209
+ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
210
+ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
211
+ golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
212
+ golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
213
+ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
214
+ golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
215
+ golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
216
+ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
217
+ golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
218
+ golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
219
+ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
220
+ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
221
+ google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
222
+ google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
223
+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
224
+ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
225
+ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
226
+ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
227
+ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
228
+ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
229
+ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
230
+ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
231
+ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
232
+ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
233
+ gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0=
234
+ gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8=
235
+ gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
236
+ gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
237
+ lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
238
+ lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
239
+ modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q=
240
+ modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y=
241
+ modernc.org/ccgo/v3 v3.16.14 h1:af6KNtFgsVmnDYrWk3PQCS9XT6BXe7o3ZFJKkIKvXNQ=
242
+ modernc.org/ccgo/v3 v3.16.14/go.mod h1:mPDSujUIaTNWQSG4eqKw+atqLOEbma6Ncsa94WbC9zo=
243
+ modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM=
244
+ modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak=
245
+ modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
246
+ modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
247
+ modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o=
248
+ modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
249
+ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
250
+ modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
251
+ modernc.org/sqlite v1.25.0 h1:AFweiwPNd/b3BoKnBOfFm+Y260guGMF+0UFk0savqeA=
252
+ modernc.org/sqlite v1.25.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU=
253
+ modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
254
+ modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
255
+ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
256
+ modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
257
+ nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
258
+ nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
259
+ rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
260
+ rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
261
+ rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
haproxy.cfg ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # global
2
+ # tune.h2.initial-window-size 1048576
3
+ # tune.h2.max-concurrent-streams 1000
4
+
5
+ defaults
6
+ mode http
7
+ log global
8
+
9
+ frontend http-in
10
+ # bind *:80
11
+ # bind *:443 ssl crt /etc/ssl/fullchain.pem alpn h2,http/1.1
12
+ bind *:8000
13
+ mode http
14
+ acl is_api path_beg /api
15
+ use_backend api-backend if is_api
16
+ default_backend frontend
17
+
18
+ backend api-backend
19
+ balance roundrobin
20
+ server go-backend server:8080 check
21
+
22
+ backend frontend
23
+ balance roundrobin
24
+ server nextjs-frontend client:3000 check
main.go ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package main
2
+
3
+ import (
4
+ "fmt"
5
+ "path/filepath"
6
+ "time"
7
+
8
+ "github.com/divyam234/teldrive/cache"
9
+ "github.com/divyam234/teldrive/database"
10
+ "github.com/divyam234/teldrive/routes"
11
+ "github.com/divyam234/teldrive/utils"
12
+
13
+ "github.com/divyam234/cors"
14
+ "github.com/divyam234/teldrive/utils/cron"
15
+ "github.com/gin-gonic/gin"
16
+ "github.com/go-co-op/gocron"
17
+ )
18
+
19
+ func main() {
20
+
21
+ gin.SetMode(gin.ReleaseMode)
22
+
23
+ router := gin.Default()
24
+
25
+ utils.InitConfig()
26
+
27
+ utils.InitializeLogger()
28
+
29
+ database.InitDB()
30
+
31
+ cache.CacheInit()
32
+
33
+ utils.InitBotClients()
34
+
35
+ cron.FilesDeleteJob()
36
+
37
+ scheduler := gocron.NewScheduler(time.UTC)
38
+
39
+ scheduler.Every(1).Hours().Do(cron.FilesDeleteJob)
40
+
41
+ router.Use(cors.New(cors.Config{
42
+ AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"},
43
+ AllowHeaders: []string{"Origin", "Content-Length", "Content-Type"},
44
+ AllowCredentials: true,
45
+ AllowOriginFunc: func(origin string) bool {
46
+ return true
47
+ },
48
+ MaxAge: 12 * time.Hour,
49
+ }))
50
+
51
+ router.Use(gin.ErrorLogger())
52
+
53
+ routes.GetRoutes(router)
54
+
55
+ config := utils.GetConfig()
56
+ certDir := filepath.Join(config.ExecDir, "sslcerts")
57
+ ok, _ := utils.PathExists(certDir)
58
+ if ok && config.Https {
59
+ router.RunTLS(fmt.Sprintf(":%d", config.Port), filepath.Join(certDir, "cert.pem"), filepath.Join(certDir, "key.pem"))
60
+ } else {
61
+ router.Run(fmt.Sprintf(":%d", config.Port))
62
+ }
63
+ scheduler.StartAsync()
64
+ }
models/file.model.go ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package models
2
+
3
+ import (
4
+ "database/sql/driver"
5
+ "encoding/json"
6
+ "time"
7
+ )
8
+
9
+ type File struct {
10
+ ID string `gorm:"type:text;primaryKey;default:generate_uid(16)"`
11
+ Name string `gorm:"type:text;not null"`
12
+ Type string `gorm:"type:text;not null"`
13
+ MimeType string `gorm:"type:text;not null"`
14
+ Path string `gorm:"type:text;index"`
15
+ Size int64 `gorm:"type:bigint"`
16
+ Starred *bool `gorm:"default:false"`
17
+ Depth *int `gorm:"type:integer"`
18
+ UserID int64 `gorm:"type:bigint;not null"`
19
+ Status string `gorm:"type:text"`
20
+ ParentID string `gorm:"type:text;index"`
21
+ Parts *Parts `gorm:"type:jsonb"`
22
+ ChannelID *int64 `gorm:"type:bigint"`
23
+ CreatedAt time.Time `gorm:"default:timezone('utc'::text, now())"`
24
+ UpdatedAt time.Time `gorm:"default:timezone('utc'::text, now())"`
25
+ }
26
+
27
+ type Parts []Part
28
+ type Part struct {
29
+ ID int64 `json:"id"`
30
+ }
31
+
32
+ func (a Parts) Value() (driver.Value, error) {
33
+ return json.Marshal(a)
34
+ }
35
+
36
+ func (a *Parts) Scan(value interface{}) error {
37
+ if err := json.Unmarshal(value.([]byte), &a); err != nil {
38
+ return err
39
+ }
40
+ return nil
41
+ }
models/upload.model.go ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package models
2
+
3
+ import (
4
+ "time"
5
+ )
6
+
7
+ type Upload struct {
8
+ ID string `gorm:"type:text;primary_key;default:generate_uid(16)"`
9
+ UploadId string `gorm:"type:text"`
10
+ Name string `gorm:"type:text"`
11
+ PartNo int `gorm:"type:integer"`
12
+ TotalParts int `gorm:"type:integer"`
13
+ PartId int `gorm:"type:integer"`
14
+ ChannelID int64 `gorm:"type:bigint"`
15
+ Size int64 `gorm:"type:bigint"`
16
+ CreatedAt time.Time `gorm:"default:timezone('utc'::text, now())"`
17
+ }
models/user.model.go ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package models
2
+
3
+ import (
4
+ "time"
5
+ )
6
+
7
+ type User struct {
8
+ UserId int64 `gorm:"type:bigint;primaryKey"`
9
+ Name string `gorm:"type:text"`
10
+ UserName string `gorm:"type:text"`
11
+ IsPremium bool `gorm:"type:bool"`
12
+ TgSession string `gorm:"type:text"`
13
+ UpdatedAt time.Time `gorm:"default:timezone('utc'::text, now())"`
14
+ CreatedAt time.Time `gorm:"default:timezone('utc'::text, now())"`
15
+ }
public/demo.png ADDED
routes/auth.go ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package routes
2
+
3
+ import (
4
+ "net/http"
5
+
6
+ "github.com/divyam234/teldrive/database"
7
+ "github.com/divyam234/teldrive/services"
8
+
9
+ "github.com/gin-gonic/gin"
10
+ )
11
+
12
+ func addAuthRoutes(rg *gin.RouterGroup) {
13
+
14
+ r := rg.Group("/auth")
15
+
16
+ authService := services.AuthService{Db: database.DB, SessionMaxAge: 30 * 24 * 60 * 60, SessionCookieName: "user-session"}
17
+
18
+ r.POST("/login", func(c *gin.Context) {
19
+
20
+ res, err := authService.LogIn(c)
21
+
22
+ if err != nil {
23
+ c.AbortWithError(err.Code, err.Error)
24
+ return
25
+ }
26
+ c.JSON(http.StatusOK, res)
27
+ })
28
+
29
+ r.GET("/logout", Authmiddleware, func(c *gin.Context) {
30
+
31
+ res, err := authService.Logout(c)
32
+
33
+ if err != nil {
34
+ c.AbortWithError(err.Code, err.Error)
35
+ return
36
+ }
37
+ c.JSON(http.StatusOK, res)
38
+
39
+ })
40
+
41
+ r.GET("/ws", authService.HandleMultipleLogin)
42
+
43
+ r.GET("/session", func(c *gin.Context) {
44
+
45
+ session := authService.GetSession(c)
46
+
47
+ c.JSON(http.StatusOK, session)
48
+ })
49
+
50
+ }
routes/file.go ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package routes
2
+
3
+ import (
4
+ "net/http"
5
+
6
+ "github.com/divyam234/teldrive/database"
7
+ "github.com/divyam234/teldrive/services"
8
+ "github.com/divyam234/teldrive/utils"
9
+
10
+ "github.com/gin-gonic/gin"
11
+ )
12
+
13
+ func addFileRoutes(rg *gin.RouterGroup) {
14
+
15
+ r := rg.Group("/files")
16
+ r.Use(Authmiddleware)
17
+ fileService := services.FileService{Db: database.DB, ChannelID: utils.GetConfig().ChannelID}
18
+
19
+ r.GET("", func(c *gin.Context) {
20
+ res, err := fileService.ListFiles(c)
21
+
22
+ if err != nil {
23
+ c.AbortWithError(err.Code, err.Error)
24
+ return
25
+ }
26
+
27
+ c.JSON(http.StatusOK, res)
28
+ })
29
+
30
+ r.POST("", func(c *gin.Context) {
31
+
32
+ res, err := fileService.CreateFile(c)
33
+
34
+ if err != nil {
35
+ c.AbortWithError(err.Code, err.Error)
36
+ return
37
+ }
38
+
39
+ c.JSON(http.StatusOK, res)
40
+ })
41
+
42
+ r.GET("/:fileID", func(c *gin.Context) {
43
+
44
+ res, err := fileService.GetFileByID(c)
45
+
46
+ if err != nil {
47
+ c.AbortWithError(http.StatusNotFound, err)
48
+ return
49
+ }
50
+
51
+ c.JSON(http.StatusOK, res)
52
+ })
53
+
54
+ r.PATCH("/:fileID", func(c *gin.Context) {
55
+
56
+ res, err := fileService.UpdateFile(c)
57
+
58
+ if err != nil {
59
+ c.AbortWithError(err.Code, err.Error)
60
+ return
61
+ }
62
+
63
+ c.JSON(http.StatusOK, res)
64
+ })
65
+
66
+ r.GET("/:fileID/:fileName", func(c *gin.Context) {
67
+
68
+ fileService.GetFileStream(c)
69
+ })
70
+
71
+ r.POST("/movefiles", func(c *gin.Context) {
72
+
73
+ res, err := fileService.MoveFiles(c)
74
+
75
+ if err != nil {
76
+ c.AbortWithError(err.Code, err.Error)
77
+ return
78
+ }
79
+
80
+ c.JSON(http.StatusOK, res)
81
+ })
82
+
83
+ r.POST("/makekdir", func(c *gin.Context) {
84
+
85
+ res, err := fileService.MakeDirectory(c)
86
+
87
+ if err != nil {
88
+ c.AbortWithError(err.Code, err.Error)
89
+ return
90
+ }
91
+
92
+ c.JSON(http.StatusOK, res)
93
+ })
94
+
95
+ r.POST("/deletefiles", func(c *gin.Context) {
96
+
97
+ res, err := fileService.DeleteFiles(c)
98
+
99
+ if err != nil {
100
+ c.AbortWithError(err.Code, err.Error)
101
+ return
102
+ }
103
+
104
+ c.JSON(http.StatusOK, res)
105
+ })
106
+
107
+ }
routes/main.go ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package routes
2
+
3
+ import "github.com/gin-gonic/gin"
4
+
5
+ func GetRoutes(router *gin.Engine) {
6
+ api := router.Group("/api")
7
+ addAuthRoutes(api)
8
+ addFileRoutes(api)
9
+ addUploadRoutes(api)
10
+ addUserRoutes(api)
11
+ }
routes/middleware.go ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package routes
2
+
3
+ import (
4
+ "net/http"
5
+ "time"
6
+
7
+ "github.com/divyam234/teldrive/utils"
8
+ "github.com/divyam234/teldrive/utils/auth"
9
+ "github.com/gin-gonic/gin"
10
+ "github.com/go-jose/go-jose/v3/jwt"
11
+ )
12
+
13
+ func Authmiddleware(c *gin.Context) {
14
+
15
+ if c.FullPath() == "/api/files/:fileID/:fileName" && utils.GetConfig().MultiClient {
16
+ c.Next()
17
+ }
18
+ cookie, err := c.Request.Cookie("user-session")
19
+
20
+ if err != nil {
21
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "missing session cookie"})
22
+ c.Abort()
23
+ return
24
+ }
25
+
26
+ now := time.Now().UTC()
27
+
28
+ jwePayload, err := auth.Decode(cookie.Value)
29
+
30
+ if err != nil {
31
+ c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
32
+ c.Abort()
33
+ return
34
+ }
35
+
36
+ if *jwePayload.Expiry < *jwt.NewNumericDate(now) {
37
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "token expired"})
38
+ c.Abort()
39
+ return
40
+ }
41
+
42
+ c.Set("jwtUser", jwePayload)
43
+
44
+ c.Next()
45
+
46
+ }
routes/upload.go ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package routes
2
+
3
+ import (
4
+ "net/http"
5
+
6
+ "github.com/divyam234/teldrive/database"
7
+ "github.com/divyam234/teldrive/services"
8
+ "github.com/divyam234/teldrive/utils"
9
+
10
+ "github.com/gin-gonic/gin"
11
+ )
12
+
13
+ func addUploadRoutes(rg *gin.RouterGroup) {
14
+
15
+ r := rg.Group("/uploads")
16
+ r.Use(Authmiddleware)
17
+
18
+ uploadService := services.UploadService{Db: database.DB, ChannelID: utils.GetConfig().ChannelID}
19
+
20
+ r.GET("/:id", func(c *gin.Context) {
21
+
22
+ res, err := uploadService.GetUploadFileById(c)
23
+
24
+ if err != nil {
25
+ c.AbortWithError(http.StatusNotFound, err.Error)
26
+ return
27
+ }
28
+
29
+ c.JSON(http.StatusOK, res)
30
+ })
31
+
32
+ r.POST("/:id", func(c *gin.Context) {
33
+
34
+ res, err := uploadService.UploadFile(c)
35
+
36
+ if err != nil {
37
+ c.AbortWithError(err.Code, err.Error)
38
+ return
39
+ }
40
+
41
+ c.JSON(http.StatusOK, res)
42
+ })
43
+
44
+ r.DELETE("/:id", func(c *gin.Context) {
45
+ err := uploadService.DeleteUploadFile(c)
46
+
47
+ if err != nil {
48
+ c.AbortWithError(err.Code, err.Error)
49
+ return
50
+ }
51
+
52
+ c.JSON(http.StatusOK, gin.H{"message": "upload deleted"})
53
+ })
54
+
55
+ }
routes/user.go ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package routes
2
+
3
+ import (
4
+ "github.com/divyam234/teldrive/services"
5
+
6
+ "github.com/gin-gonic/gin"
7
+ )
8
+
9
+ func addUserRoutes(rg *gin.RouterGroup) {
10
+ r := rg.Group("/users")
11
+ r.Use(Authmiddleware)
12
+ userService := services.UserService{}
13
+
14
+ r.GET("/profile", func(c *gin.Context) {
15
+ if c.Query("photo") != "" {
16
+ userService.GetProfilePhoto(c)
17
+ }
18
+ })
19
+ }
schemas/common.go ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ package schemas
2
+
3
+ type Message struct {
4
+ Status bool `json:"status"`
5
+ Message string `json:"message,omitempty"`
6
+ }
schemas/file.schema.go ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package schemas
2
+
3
+ import (
4
+ "time"
5
+
6
+ "github.com/divyam234/teldrive/models"
7
+ )
8
+
9
+ type PaginationQuery struct {
10
+ PerPage int `form:"perPage"`
11
+ NextPageToken string `form:"nextPageToken"`
12
+ }
13
+
14
+ type SortingQuery struct {
15
+ Sort string `form:"sort"`
16
+ Order string `form:"order"`
17
+ }
18
+
19
+ type FileQuery struct {
20
+ Name string `form:"name" mapstructure:"name,omitempty"`
21
+ Search string `form:"search" mapstructure:"search,omitempty"`
22
+ Type string `form:"type" mapstructure:"type,omitempty"`
23
+ Path string `form:"path" mapstructure:"path,omitempty"`
24
+ Op string `form:"op" mapstructure:"op,omitempty"`
25
+ Starred *bool `form:"starred" mapstructure:"starred,omitempty"`
26
+ MimeType string `form:"mimeType" mapstructure:"mime_type,omitempty"`
27
+ ParentID string `form:"parentId" mapstructure:"parent_id,omitempty"`
28
+ UpdatedAt *time.Time `form:"updatedAt" mapstructure:"updated_at,omitempty"`
29
+ Status string `mapstructure:"status"`
30
+ UserID int64 `mapstructure:"user_id"`
31
+ }
32
+
33
+ type FileIn struct {
34
+ Name string `json:"name" mapstructure:"name,omitempty"`
35
+ Type string `json:"type" mapstructure:"type,omitempty"`
36
+ Parts *models.Parts `json:"parts,omitempty" mapstructure:"parts,omitempty"`
37
+ MimeType string `json:"mimeType" mapstructure:"mime_type,omitempty"`
38
+ ChannelID *int64 `json:"channelId,omitempty" mapstructure:"channel_id,omitempty"`
39
+ Path string `json:"path" mapstructure:"path,omitempty"`
40
+ Size int64 `json:"size" mapstructure:"size,omitempty"`
41
+ Starred *bool `json:"starred" mapstructure:"starred,omitempty"`
42
+ Depth *int `json:"depth,omitempty" mapstructure:"depth,omitempty"`
43
+ Status string `mapstructure:"status,omitempty"`
44
+ UserID int64 `json:"userId" mapstructure:"user_id,omitempty"`
45
+ ParentID string `json:"parentId" mapstructure:"parent_id,omitempty"`
46
+ }
47
+
48
+ type FileOut struct {
49
+ ID string `json:"id"`
50
+ Name string `json:"name"`
51
+ Type string `json:"type"`
52
+ MimeType string `json:"mimeType" mapstructure:"mime_type"`
53
+ Path string `json:"path,omitempty" mapstructure:"path,omitempty"`
54
+ Size int64 `json:"size,omitempty" mapstructure:"size,omitempty"`
55
+ Starred *bool `json:"starred"`
56
+ ParentID string `json:"parentId,omitempty" mapstructure:"parent_id"`
57
+ UpdatedAt time.Time `json:"updatedAt,omitempty" mapstructure:"updated_at"`
58
+ }
59
+
60
+ type FileResponse struct {
61
+ Results []FileOut `json:"results"`
62
+ NextPageToken string `json:"nextPageToken,omitempty"`
63
+ }
64
+
65
+ type FileOutFull struct {
66
+ FileOut
67
+ Parts *models.Parts `json:"parts,omitempty"`
68
+ ChannelID *int64 `json:"channelId"`
69
+ }
70
+
71
+ type FileOperation struct {
72
+ Files []string `json:"files"`
73
+ Destination string `json:"destination,omitempty"`
74
+ }
75
+
76
+ type MkDir struct {
77
+ Path string `json:"path"`
78
+ }
schemas/upload.schema.go ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package schemas
2
+
3
+ type UploadQuery struct {
4
+ Filename string `form:"fileName"`
5
+ PartNo int `form:"partNo,omitempty"`
6
+ TotalParts int `form:"totalparts"`
7
+ }
8
+
9
+ type UploadPartOut struct {
10
+ ID string `json:"id"`
11
+ Name string `json:"name"`
12
+ PartId int `json:"partId"`
13
+ PartNo int `json:"partNo"`
14
+ TotalParts int `json:"totalParts"`
15
+ ChannelID int64 `json:"channelId"`
16
+ Size int64 `json:"size"`
17
+ }
18
+
19
+ type UploadOut struct {
20
+ Parts []UploadPartOut `json:"parts"`
21
+ }
services/auth.service.go ADDED
@@ -0,0 +1,347 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package services
2
+
3
+ import (
4
+ "bytes"
5
+ "context"
6
+ "encoding/base64"
7
+ "encoding/binary"
8
+ "encoding/json"
9
+ "errors"
10
+ "fmt"
11
+ "log"
12
+ "math/big"
13
+ "net"
14
+ "net/http"
15
+ "strconv"
16
+ "time"
17
+
18
+ "github.com/divyam234/teldrive/models"
19
+ "github.com/divyam234/teldrive/schemas"
20
+ "github.com/divyam234/teldrive/types"
21
+ "github.com/divyam234/teldrive/utils"
22
+ "github.com/divyam234/teldrive/utils/auth"
23
+ "github.com/gin-gonic/gin"
24
+ "github.com/go-jose/go-jose/v3/jwt"
25
+ "github.com/gorilla/websocket"
26
+ "github.com/gotd/td/session"
27
+ tgauth "github.com/gotd/td/telegram/auth"
28
+ "github.com/gotd/td/telegram/auth/qrlogin"
29
+ "github.com/gotd/td/tg"
30
+ "github.com/gotd/td/tgerr"
31
+ "gorm.io/gorm"
32
+ )
33
+
34
+ type AuthService struct {
35
+ Db *gorm.DB
36
+ SessionMaxAge int
37
+ SessionCookieName string
38
+ }
39
+
40
+ type SessionData struct {
41
+ Version int
42
+ Data session.Data
43
+ }
44
+ type SocketMessage struct {
45
+ AuthType string `json:"authType"`
46
+ Message string `json:"message"`
47
+ PhoneNo string `json:"phoneNo,omitempty"`
48
+ PhoneCodeHash string `json:"phoneCodeHash,omitempty"`
49
+ PhoneCode string `json:"phoneCode,omitempty"`
50
+ Password string `json:"password,omitempty"`
51
+ }
52
+
53
+ func IP4toInt(IPv4Address net.IP) int64 {
54
+ IPv4Int := big.NewInt(0)
55
+ IPv4Int.SetBytes(IPv4Address.To4())
56
+ return IPv4Int.Int64()
57
+ }
58
+
59
+ func Pack32BinaryIP4(ip4Address string) []byte {
60
+ ipv4Decimal := IP4toInt(net.ParseIP(ip4Address))
61
+
62
+ buf := new(bytes.Buffer)
63
+ binary.Write(buf, binary.BigEndian, uint32(ipv4Decimal))
64
+ return buf.Bytes()
65
+ }
66
+
67
+ func generateTgSession(dcID int, authKey []byte, port int) string {
68
+
69
+ dcMaps := map[int]string{
70
+ 1: "149.154.175.53",
71
+ 2: "149.154.167.51",
72
+ 3: "149.154.175.100",
73
+ 4: "149.154.167.91",
74
+ 5: "91.108.56.130",
75
+ }
76
+
77
+ dcIDByte := byte(dcID)
78
+ serverAddressBytes := Pack32BinaryIP4(dcMaps[dcID])
79
+ portByte := make([]byte, 2)
80
+ binary.BigEndian.PutUint16(portByte, uint16(port))
81
+
82
+ packet := make([]byte, 0)
83
+ packet = append(packet, dcIDByte)
84
+ packet = append(packet, serverAddressBytes...)
85
+ packet = append(packet, portByte...)
86
+ packet = append(packet, authKey...)
87
+
88
+ base64Encoded := base64.URLEncoding.EncodeToString(packet)
89
+ return "1" + base64Encoded
90
+ }
91
+
92
+ func setCookie(c *gin.Context, key string, value string, age int) {
93
+
94
+ config := utils.GetConfig()
95
+
96
+ if config.CookieSameSite {
97
+ c.SetSameSite(2)
98
+ } else {
99
+ c.SetSameSite(4)
100
+ }
101
+ c.SetCookie(key, value, age, "/", c.Request.Host, config.Https, true)
102
+
103
+ }
104
+
105
+ func (as *AuthService) LogIn(c *gin.Context) (*schemas.Message, *types.AppError) {
106
+ var session types.TgSession
107
+ if err := c.ShouldBindJSON(&session); err != nil {
108
+ return nil, &types.AppError{Error: errors.New("invalid request payload"), Code: http.StatusBadRequest}
109
+ }
110
+
111
+ now := time.Now().UTC()
112
+
113
+ jwtClaims := &types.JWTClaims{Claims: jwt.Claims{
114
+ Subject: strconv.FormatInt(session.UserID, 10),
115
+ IssuedAt: jwt.NewNumericDate(now),
116
+ Expiry: jwt.NewNumericDate(now.Add(time.Duration(as.SessionMaxAge) * time.Second)),
117
+ }, TgSession: session.Sesssion,
118
+ Name: session.Name,
119
+ UserName: session.UserName,
120
+ Bot: session.Bot,
121
+ IsPremium: session.IsPremium,
122
+ }
123
+
124
+ jweToken, err := auth.Encode(jwtClaims)
125
+
126
+ if err != nil {
127
+ return nil, &types.AppError{Error: err, Code: http.StatusBadRequest}
128
+ }
129
+
130
+ user := models.User{
131
+ UserId: session.UserID,
132
+ Name: session.Name,
133
+ UserName: session.UserName,
134
+ IsPremium: session.IsPremium,
135
+ TgSession: session.Sesssion,
136
+ }
137
+
138
+ var result []models.User
139
+
140
+ if err := as.Db.Model(&models.User{}).Where("user_id = ?", session.UserID).Find(&result).Error; err != nil {
141
+ return nil, &types.AppError{Error: errors.New("failed to create or update user"), Code: http.StatusInternalServerError}
142
+ }
143
+ if len(result) == 0 {
144
+ if err := as.Db.Create(&user).Error; err != nil {
145
+ return nil, &types.AppError{Error: errors.New("failed to create or update user"), Code: http.StatusInternalServerError}
146
+ }
147
+ //Create root folder on first login
148
+
149
+ file := &models.File{
150
+ Name: "root",
151
+ Type: "folder",
152
+ MimeType: "drive/folder",
153
+ Path: "/",
154
+ Depth: utils.IntPointer(0),
155
+ UserID: session.UserID,
156
+ Status: "active",
157
+ ParentID: "root",
158
+ }
159
+ if err := as.Db.Create(file).Error; err != nil {
160
+ return nil, &types.AppError{Error: errors.New("failed to create or update user"), Code: http.StatusInternalServerError}
161
+ }
162
+ } else {
163
+ if err := as.Db.Model(&models.User{}).Where("user_id = ?", session.UserID).Update("tg_session", session.Sesssion).Error; err != nil {
164
+ return nil, &types.AppError{Error: errors.New("failed to create or update user"), Code: http.StatusInternalServerError}
165
+ }
166
+ }
167
+ setCookie(c, as.SessionCookieName, jweToken, as.SessionMaxAge)
168
+ return &schemas.Message{Status: true, Message: "login success"}, nil
169
+ }
170
+
171
+ func (as *AuthService) GetSession(c *gin.Context) *types.Session {
172
+
173
+ cookie, err := c.Request.Cookie(as.SessionCookieName)
174
+
175
+ if err != nil {
176
+ return nil
177
+ }
178
+
179
+ jwePayload, err := auth.Decode(cookie.Value)
180
+
181
+ if err != nil {
182
+ return nil
183
+ }
184
+
185
+ now := time.Now().UTC()
186
+
187
+ newExpires := now.Add(time.Duration(as.SessionMaxAge) * time.Second)
188
+
189
+ session := &types.Session{Name: jwePayload.Name, UserName: jwePayload.UserName, Expires: newExpires.Format(time.RFC3339)}
190
+
191
+ jwePayload.IssuedAt = jwt.NewNumericDate(now)
192
+
193
+ jwePayload.Expiry = jwt.NewNumericDate(newExpires)
194
+
195
+ jweToken, err := auth.Encode(jwePayload)
196
+
197
+ if err != nil {
198
+ return nil
199
+ }
200
+ setCookie(c, as.SessionCookieName, jweToken, as.SessionMaxAge)
201
+ return session
202
+ }
203
+
204
+ func (as *AuthService) Logout(c *gin.Context) (*schemas.Message, *types.AppError) {
205
+ val, _ := c.Get("jwtUser")
206
+ jwtUser := val.(*types.JWTClaims)
207
+ userId, _ := strconv.ParseInt(jwtUser.Subject, 10, 64)
208
+
209
+ client, _ := utils.GetAuthClient(c, jwtUser.TgSession, userId)
210
+
211
+ client.Run(c, func(ctx context.Context) error {
212
+ _, err := client.API().AuthLogOut(c)
213
+ return err
214
+ })
215
+
216
+ setCookie(c, as.SessionCookieName, "", -1)
217
+ return &schemas.Message{Status: true, Message: "logout success"}, nil
218
+ }
219
+
220
+ func prepareSession(user *tg.User, data *session.Data) *types.TgSession {
221
+ sessionString := generateTgSession(data.DC, data.AuthKey, 443)
222
+ session := &types.TgSession{
223
+ Sesssion: sessionString,
224
+ UserID: user.ID,
225
+ Bot: user.Bot,
226
+ UserName: user.Username,
227
+ Name: fmt.Sprintf("%s %s", user.FirstName, user.LastName),
228
+ IsPremium: user.Premium,
229
+ }
230
+ return session
231
+ }
232
+
233
+ func (as *AuthService) HandleMultipleLogin(c *gin.Context) {
234
+ upgrader := websocket.Upgrader{
235
+ CheckOrigin: func(r *http.Request) bool {
236
+ return true
237
+ },
238
+ }
239
+ conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
240
+ if err != nil {
241
+ log.Println(err)
242
+ return
243
+ }
244
+ defer conn.Close()
245
+
246
+ dispatcher := tg.NewUpdateDispatcher()
247
+ loggedIn := qrlogin.OnLoginToken(dispatcher)
248
+ sessionStorage := &session.StorageMemory{}
249
+ tgClient, stop, _ := utils.StartNonAuthClient(dispatcher, sessionStorage)
250
+
251
+ defer stop()
252
+
253
+ for {
254
+ message := &SocketMessage{}
255
+ err := conn.ReadJSON(message)
256
+
257
+ if err != nil {
258
+ log.Println(err)
259
+ return
260
+ }
261
+ if message.AuthType == "qr" {
262
+ go func() {
263
+ authorization, err := tgClient.QR().Auth(c, loggedIn, func(ctx context.Context, token qrlogin.Token) error {
264
+ conn.WriteJSON(map[string]interface{}{"type": "auth", "payload": map[string]string{"token": token.URL()}})
265
+ return nil
266
+ })
267
+
268
+ if tgerr.Is(err, "SESSION_PASSWORD_NEEDED") {
269
+ conn.WriteJSON(map[string]interface{}{"type": "auth", "message": "2FA required"})
270
+ return
271
+ }
272
+
273
+ if err != nil {
274
+ conn.WriteJSON(map[string]interface{}{"type": "error", "message": err.Error()})
275
+ return
276
+ }
277
+ user, ok := authorization.User.AsNotEmpty()
278
+ if !ok {
279
+ conn.WriteJSON(map[string]interface{}{"type": "error", "message": errors.New("auth failed")})
280
+ return
281
+ }
282
+ res, _ := sessionStorage.LoadSession(c)
283
+ sessionData := &SessionData{}
284
+ json.Unmarshal(res, sessionData)
285
+ session := prepareSession(user, &sessionData.Data)
286
+ conn.WriteJSON(map[string]interface{}{"type": "auth", "payload": session, "message": "success"})
287
+ }()
288
+ }
289
+ if message.AuthType == "phone" && message.Message == "sendcode" {
290
+ go func() {
291
+ res, err := tgClient.Auth().SendCode(c, message.PhoneNo, tgauth.SendCodeOptions{})
292
+ if err != nil {
293
+ conn.WriteJSON(map[string]interface{}{"type": "error", "message": err.Error()})
294
+ return
295
+ }
296
+ code := res.(*tg.AuthSentCode)
297
+ conn.WriteJSON(map[string]interface{}{"type": "auth", "payload": map[string]string{"phoneCodeHash": code.PhoneCodeHash}})
298
+ }()
299
+ }
300
+ if message.AuthType == "phone" && message.Message == "signin" {
301
+ go func() {
302
+ auth, err := tgClient.Auth().SignIn(c, message.PhoneNo, message.PhoneCode, message.PhoneCodeHash)
303
+
304
+ if errors.Is(err, tgauth.ErrPasswordAuthNeeded) {
305
+ conn.WriteJSON(map[string]interface{}{"type": "auth", "message": "2FA required"})
306
+ return
307
+ }
308
+
309
+ if err != nil {
310
+ conn.WriteJSON(map[string]interface{}{"type": "error", "message": err.Error()})
311
+ return
312
+ }
313
+ user, ok := auth.User.AsNotEmpty()
314
+ if !ok {
315
+ conn.WriteJSON(map[string]interface{}{"type": "error", "message": errors.New("auth failed")})
316
+ return
317
+ }
318
+ res, _ := sessionStorage.LoadSession(c)
319
+ sessionData := &SessionData{}
320
+ json.Unmarshal(res, sessionData)
321
+ session := prepareSession(user, &sessionData.Data)
322
+ conn.WriteJSON(map[string]interface{}{"type": "auth", "payload": session, "message": "success"})
323
+ }()
324
+ }
325
+
326
+ if message.AuthType == "2fa" && message.Password != "" {
327
+ go func() {
328
+ auth, err := tgClient.Auth().Password(c, message.Password)
329
+ if err != nil {
330
+ conn.WriteJSON(map[string]interface{}{"type": "error", "message": err.Error()})
331
+ return
332
+ }
333
+ user, ok := auth.User.AsNotEmpty()
334
+ if !ok {
335
+ conn.WriteJSON(map[string]interface{}{"type": "error", "message": errors.New("auth failed")})
336
+ return
337
+ }
338
+ res, _ := sessionStorage.LoadSession(c)
339
+ sessionData := &SessionData{}
340
+ json.Unmarshal(res, sessionData)
341
+ session := prepareSession(user, &sessionData.Data)
342
+ conn.WriteJSON(map[string]interface{}{"type": "auth", "payload": session, "message": "success"})
343
+ }()
344
+ }
345
+ }
346
+
347
+ }
services/file.service.go ADDED
@@ -0,0 +1,582 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package services
2
+
3
+ import (
4
+ "context"
5
+ "encoding/base64"
6
+ "errors"
7
+ "fmt"
8
+ "io"
9
+ "math"
10
+ "net/http"
11
+ "strconv"
12
+ "strings"
13
+
14
+ "github.com/divyam234/teldrive/cache"
15
+ "github.com/divyam234/teldrive/models"
16
+ "github.com/divyam234/teldrive/schemas"
17
+ "github.com/divyam234/teldrive/utils"
18
+
19
+ "github.com/divyam234/teldrive/types"
20
+
21
+ "github.com/gin-gonic/gin"
22
+ "github.com/gotd/td/telegram"
23
+ "github.com/gotd/td/tg"
24
+ "github.com/jackc/pgx/v5/pgconn"
25
+ "github.com/mitchellh/mapstructure"
26
+ range_parser "github.com/quantumsheep/range-parser"
27
+ "gorm.io/gorm"
28
+ "gorm.io/gorm/clause"
29
+ )
30
+
31
+ type FileService struct {
32
+ Db *gorm.DB
33
+ ChannelID int64
34
+ }
35
+
36
+ func getAuthUserId(c *gin.Context) int64 {
37
+ val, _ := c.Get("jwtUser")
38
+ jwtUser := val.(*types.JWTClaims)
39
+ userId, _ := strconv.ParseInt(jwtUser.Subject, 10, 64)
40
+ return userId
41
+ }
42
+
43
+ func (fs *FileService) CreateFile(c *gin.Context) (*schemas.FileOut, *types.AppError) {
44
+ userId := getAuthUserId(c)
45
+ var fileIn schemas.FileIn
46
+ if err := c.ShouldBindJSON(&fileIn); err != nil {
47
+ return nil, &types.AppError{Error: errors.New("invalid request payload"), Code: http.StatusBadRequest}
48
+ }
49
+
50
+ fileIn.Path = strings.TrimSpace(fileIn.Path)
51
+
52
+ if fileIn.Path != "" {
53
+ var parent models.File
54
+ if err := fs.Db.Where("type = ? AND path = ?", "folder", fileIn.Path).First(&parent).Error; err != nil {
55
+ return nil, &types.AppError{Error: errors.New("parent directory not found"), Code: http.StatusNotFound}
56
+ }
57
+ fileIn.ParentID = parent.ID
58
+ }
59
+
60
+ if fileIn.Type == "folder" {
61
+ fileIn.MimeType = "drive/folder"
62
+ var fullPath string
63
+ if fileIn.Path == "/" {
64
+ fullPath = "/" + fileIn.Name
65
+ } else {
66
+ fullPath = fileIn.Path + "/" + fileIn.Name
67
+ }
68
+ fileIn.Path = fullPath
69
+ fileIn.Depth = utils.IntPointer(len(strings.Split(fileIn.Path, "/")) - 1)
70
+ } else if fileIn.Type == "file" {
71
+ fileIn.Path = ""
72
+ fileIn.ChannelID = &fs.ChannelID
73
+ }
74
+
75
+ fileIn.UserID = userId
76
+ fileIn.Starred = utils.BoolPointer(false)
77
+ fileIn.Status = "active"
78
+
79
+ fileDb := mapFileInToFile(fileIn)
80
+
81
+ if err := fs.Db.Create(&fileDb).Error; err != nil {
82
+ pgErr := err.(*pgconn.PgError)
83
+ if pgErr.Code == "23505" {
84
+ return nil, &types.AppError{Error: errors.New("file exists"), Code: http.StatusBadRequest}
85
+ }
86
+ return nil, &types.AppError{Error: errors.New("failed to create a file"), Code: http.StatusBadRequest}
87
+
88
+ }
89
+
90
+ res := mapFileToFileOut(fileDb)
91
+
92
+ return &res, nil
93
+ }
94
+
95
+ func (fs *FileService) UpdateFile(c *gin.Context) (*schemas.FileOut, *types.AppError) {
96
+
97
+ fileID := c.Param("fileID")
98
+
99
+ var fileUpdate schemas.FileIn
100
+
101
+ var files []models.File
102
+
103
+ if err := c.ShouldBindJSON(&fileUpdate); err != nil {
104
+ return nil, &types.AppError{Error: errors.New("invalid request payload"), Code: http.StatusBadRequest}
105
+ }
106
+
107
+ if fileUpdate.Type == "folder" && fileUpdate.Name != "" {
108
+ if err := fs.Db.Raw("select * from teldrive.update_folder(?, ?)", fileID, fileUpdate.Name).Scan(&files).Error; err != nil {
109
+ return nil, &types.AppError{Error: errors.New("failed to update the file"), Code: http.StatusInternalServerError}
110
+ }
111
+ } else {
112
+ fileDb := mapFileInToFile(fileUpdate)
113
+ if err := fs.Db.Model(&files).Clauses(clause.Returning{}).Where("id = ?", fileID).Updates(fileDb).Error; err != nil {
114
+ return nil, &types.AppError{Error: errors.New("failed to update the file"), Code: http.StatusInternalServerError}
115
+ }
116
+ }
117
+
118
+ if len(files) == 0 {
119
+ return nil, &types.AppError{Error: errors.New("file not updated"), Code: http.StatusNotFound}
120
+ }
121
+
122
+ file := mapFileToFileOut(files[0])
123
+
124
+ return &file, nil
125
+
126
+ }
127
+
128
+ func (fs *FileService) GetFileByID(c *gin.Context) (*schemas.FileOutFull, error) {
129
+
130
+ fileID := c.Param("fileID")
131
+
132
+ var file []models.File
133
+
134
+ fs.Db.Model(&models.File{}).Where("id = ?", fileID).Find(&file)
135
+
136
+ if len(file) == 0 {
137
+ return nil, errors.New("file not found")
138
+ }
139
+
140
+ return mapFileToFileOutFull(file[0]), nil
141
+ }
142
+
143
+ func (fs *FileService) ListFiles(c *gin.Context) (*schemas.FileResponse, *types.AppError) {
144
+
145
+ userId := getAuthUserId(c)
146
+
147
+ var pagingParams schemas.PaginationQuery
148
+ pagingParams.PerPage = 200
149
+ if err := c.ShouldBindQuery(&pagingParams); err != nil {
150
+ return nil, &types.AppError{Error: errors.New(""), Code: http.StatusBadRequest}
151
+ }
152
+
153
+ var sortingParams schemas.SortingQuery
154
+ sortingParams.Order = "asc"
155
+ sortingParams.Sort = "name"
156
+ if err := c.ShouldBindQuery(&sortingParams); err != nil {
157
+ return nil, &types.AppError{Error: errors.New(""), Code: http.StatusBadRequest}
158
+ }
159
+
160
+ var fileQuery schemas.FileQuery
161
+ fileQuery.Op = "list"
162
+ fileQuery.Status = "active"
163
+ fileQuery.UserID = userId
164
+ if err := c.ShouldBindQuery(&fileQuery); err != nil {
165
+ return nil, &types.AppError{Error: errors.New(""), Code: http.StatusBadRequest}
166
+ }
167
+
168
+ query := fs.Db.Model(&models.File{}).Limit(pagingParams.PerPage).
169
+ Where(map[string]interface{}{"user_id": userId, "status": "active"})
170
+
171
+ if fileQuery.Op == "list" {
172
+
173
+ if pathExists, message := fs.CheckIfPathExists(&fileQuery.Path); !pathExists {
174
+ return nil, &types.AppError{Error: errors.New(message), Code: http.StatusNotFound}
175
+ }
176
+
177
+ setOrderFilter(query, &pagingParams, &sortingParams)
178
+
179
+ query.Order("type DESC").Order(getOrder(sortingParams)).
180
+ Where("parent_id in (?)", fs.Db.Model(&models.File{}).Select("id").Where("path = ?", fileQuery.Path))
181
+
182
+ } else if fileQuery.Op == "find" {
183
+
184
+ filterQuery := map[string]interface{}{}
185
+
186
+ err := mapstructure.Decode(fileQuery, &filterQuery)
187
+
188
+ if err != nil {
189
+ return nil, &types.AppError{Error: err, Code: http.StatusBadRequest}
190
+ }
191
+
192
+ delete(filterQuery, "op")
193
+
194
+ if filterQuery["updated_at"] == nil {
195
+ delete(filterQuery, "updated_at")
196
+ }
197
+
198
+ setOrderFilter(query, &pagingParams, &sortingParams)
199
+
200
+ query.Order("type DESC").Order(getOrder(sortingParams)).Where(filterQuery)
201
+
202
+ } else if fileQuery.Op == "search" {
203
+
204
+ query.Where("teldrive.get_tsquery(?) @@ teldrive.get_tsvector(name)", fileQuery.Search)
205
+
206
+ setOrderFilter(query, &pagingParams, &sortingParams)
207
+ query.Order(getOrder(sortingParams))
208
+
209
+ }
210
+
211
+ var results []schemas.FileOut
212
+
213
+ query.Find(&results)
214
+
215
+ token := ""
216
+
217
+ if len(results) == pagingParams.PerPage {
218
+ lastItem := results[len(results)-1]
219
+ token = utils.GetField(&lastItem, utils.CamelToPascalCase(sortingParams.Sort))
220
+ token = base64.StdEncoding.EncodeToString([]byte(token))
221
+ }
222
+
223
+ res := &schemas.FileResponse{Results: results, NextPageToken: token}
224
+
225
+ return res, nil
226
+ }
227
+
228
+ func (fs *FileService) CheckIfPathExists(path *string) (bool, string) {
229
+ query := fs.Db.Model(&models.File{}).Select("id").Where("path = ?", path)
230
+ var results []schemas.FileOut
231
+ query.Find(&results)
232
+ if len(results) == 0 {
233
+ return false, "This directory doesn't exist."
234
+ }
235
+ return true, ""
236
+ }
237
+
238
+ func (fs *FileService) MakeDirectory(c *gin.Context) (*schemas.FileOut, *types.AppError) {
239
+
240
+ var payload schemas.MkDir
241
+
242
+ var files []models.File
243
+
244
+ if err := c.ShouldBindJSON(&payload); err != nil {
245
+ return nil, &types.AppError{Error: errors.New("invalid request payload"), Code: http.StatusBadRequest}
246
+ }
247
+
248
+ userId := getAuthUserId(c)
249
+ if err := fs.Db.Raw("select * from teldrive.create_directories(?, ?)", userId, payload.Path).Scan(&files).Error; err != nil {
250
+ return nil, &types.AppError{Error: errors.New("failed to create directories"), Code: http.StatusInternalServerError}
251
+ }
252
+
253
+ file := mapFileToFileOut(files[0])
254
+
255
+ return &file, nil
256
+
257
+ }
258
+
259
+ func (fs *FileService) MoveFiles(c *gin.Context) (*schemas.Message, *types.AppError) {
260
+
261
+ var payload schemas.FileOperation
262
+
263
+ if err := c.ShouldBindJSON(&payload); err != nil {
264
+ return nil, &types.AppError{Error: errors.New("invalid request payload"), Code: http.StatusBadRequest}
265
+ }
266
+
267
+ var destination models.File
268
+
269
+ if pathExists, message := fs.CheckIfPathExists(&payload.Destination); !pathExists {
270
+ return nil, &types.AppError{Error: errors.New(message), Code: http.StatusBadRequest}
271
+ }
272
+
273
+ if err := fs.Db.Model(&models.File{}).Select("id").Where("path = ?", payload.Destination).First(&destination).Error; errors.Is(err, gorm.ErrRecordNotFound) {
274
+ return nil, &types.AppError{Error: errors.New("destination not found"), Code: http.StatusNotFound}
275
+
276
+ }
277
+
278
+ if err := fs.Db.Model(&models.File{}).Where("id IN ?", payload.Files).UpdateColumn("parent_id", destination.ID).Error; err != nil {
279
+ return nil, &types.AppError{Error: errors.New("move failed"), Code: http.StatusInternalServerError}
280
+ }
281
+
282
+ return &schemas.Message{Status: true, Message: "files moved"}, nil
283
+ }
284
+
285
+ func (fs *FileService) DeleteFiles(c *gin.Context) (*schemas.Message, *types.AppError) {
286
+
287
+ var payload schemas.FileOperation
288
+
289
+ if err := c.ShouldBindJSON(&payload); err != nil {
290
+ return nil, &types.AppError{Error: errors.New("invalid request payload"), Code: http.StatusBadRequest}
291
+ }
292
+
293
+ if err := fs.Db.Exec("call teldrive.delete_files($1)", payload.Files).Error; err != nil {
294
+ return nil, &types.AppError{Error: errors.New("failed to delete files"), Code: http.StatusInternalServerError}
295
+ }
296
+
297
+ return &schemas.Message{Status: true, Message: "files deleted"}, nil
298
+ }
299
+
300
+ func (fs *FileService) GetFileStream(c *gin.Context) {
301
+
302
+ w := c.Writer
303
+ r := c.Request
304
+
305
+ fileID := c.Param("fileID")
306
+
307
+ var err error
308
+
309
+ res, err := cache.CachedFunction(fs.GetFileByID, fmt.Sprintf("files:%s", fileID))(c)
310
+
311
+ if err != nil {
312
+ http.Error(w, err.Error(), http.StatusBadRequest)
313
+ return
314
+ }
315
+
316
+ file := res.(*schemas.FileOutFull)
317
+
318
+ w.Header().Set("Accept-Ranges", "bytes")
319
+
320
+ var start, end int64
321
+
322
+ rangeHeader := r.Header.Get("Range")
323
+
324
+ if rangeHeader == "" {
325
+ start = 0
326
+ end = file.Size - 1
327
+ w.WriteHeader(http.StatusOK)
328
+ } else {
329
+ ranges, err := range_parser.Parse(file.Size, r.Header.Get("Range"))
330
+ if err != nil {
331
+ http.Error(w, err.Error(), http.StatusBadRequest)
332
+ return
333
+ }
334
+ start = ranges[0].Start
335
+ end = ranges[0].End
336
+ w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, file.Size))
337
+ w.WriteHeader(http.StatusPartialContent)
338
+ }
339
+
340
+ contentLength := end - start + 1
341
+
342
+ w.Header().Set("Content-Type", file.MimeType)
343
+
344
+ w.Header().Set("Content-Length", strconv.FormatInt(contentLength, 10))
345
+
346
+ disposition := "inline"
347
+
348
+ if c.Query("d") == "1" {
349
+ disposition = "attachment"
350
+ }
351
+
352
+ w.Header().Set("Content-Disposition", fmt.Sprintf("%s; filename=\"%s\"", disposition, file.Name))
353
+
354
+ client, idx := utils.GetDownloadClient(c)
355
+
356
+ defer func() {
357
+ utils.GetClientWorkload().Dec(idx)
358
+ }()
359
+
360
+ ir, iw := io.Pipe()
361
+ parts, err := fs.getParts(c, client, file)
362
+ if err != nil {
363
+ return
364
+ }
365
+ parts = rangedParts(parts, int64(start), int64(end))
366
+
367
+ if r.Method != "HEAD" {
368
+ go func() {
369
+ defer iw.Close()
370
+ for _, part := range parts {
371
+ streamFilePart(c, client, iw, &part, part.Start, part.End, 1024*1024)
372
+ }
373
+ }()
374
+ io.CopyN(w, ir, contentLength)
375
+ }
376
+
377
+ }
378
+
379
+ func (fs *FileService) getParts(ctx context.Context, tgClient *telegram.Client, file *schemas.FileOutFull) ([]types.Part, error) {
380
+
381
+ ids := []tg.InputMessageID{}
382
+
383
+ for _, part := range *file.Parts {
384
+ ids = append(ids, tg.InputMessageID{ID: int(part.ID)})
385
+ }
386
+
387
+ s := make([]tg.InputMessageClass, len(ids))
388
+
389
+ for i := range ids {
390
+ s[i] = &ids[i]
391
+ }
392
+
393
+ api := tgClient.API()
394
+
395
+ res, err := cache.CachedFunction(utils.GetChannelById, fmt.Sprintf("channels:%d", fs.ChannelID))(ctx, api, fs.ChannelID)
396
+
397
+ if err != nil {
398
+ return nil, err
399
+ }
400
+
401
+ channel := res.(*tg.Channel)
402
+
403
+ messageRequest := tg.ChannelsGetMessagesRequest{Channel: &tg.InputChannel{ChannelID: fs.ChannelID, AccessHash: channel.AccessHash},
404
+ ID: s}
405
+
406
+ res, err = cache.CachedFunction(api.ChannelsGetMessages, fmt.Sprintf("messages:%s", file.ID))(ctx, &messageRequest)
407
+
408
+ if err != nil {
409
+ return nil, err
410
+ }
411
+
412
+ messages := res.(*tg.MessagesChannelMessages)
413
+
414
+ parts := []types.Part{}
415
+
416
+ for _, message := range messages.Messages {
417
+ item := message.(*tg.Message)
418
+ media := item.Media.(*tg.MessageMediaDocument)
419
+ document := media.Document.(*tg.Document)
420
+ location := document.AsInputDocumentFileLocation()
421
+ parts = append(parts, types.Part{Location: location, Start: 0, End: document.Size - 1, Size: document.Size})
422
+ }
423
+ return parts, nil
424
+ }
425
+
426
+ func mapFileToFileOut(file models.File) schemas.FileOut {
427
+ return schemas.FileOut{
428
+ ID: file.ID,
429
+ Name: file.Name,
430
+ Type: file.Type,
431
+ MimeType: file.MimeType,
432
+ Path: file.Path,
433
+ Size: file.Size,
434
+ Starred: file.Starred,
435
+ ParentID: file.ParentID,
436
+ UpdatedAt: file.UpdatedAt,
437
+ }
438
+ }
439
+
440
+ func mapFileInToFile(file schemas.FileIn) models.File {
441
+ return models.File{
442
+ Name: file.Name,
443
+ Type: file.Type,
444
+ MimeType: file.MimeType,
445
+ Path: file.Path,
446
+ Size: file.Size,
447
+ Starred: file.Starred,
448
+ Depth: file.Depth,
449
+ UserID: file.UserID,
450
+ ParentID: file.ParentID,
451
+ Parts: file.Parts,
452
+ ChannelID: file.ChannelID,
453
+ Status: file.Status,
454
+ }
455
+ }
456
+
457
+ func mapFileToFileOutFull(file models.File) *schemas.FileOutFull {
458
+ return &schemas.FileOutFull{
459
+ FileOut: mapFileToFileOut(file),
460
+ Parts: file.Parts, ChannelID: file.ChannelID,
461
+ }
462
+ }
463
+
464
+ func setOrderFilter(query *gorm.DB, pagingParams *schemas.PaginationQuery, sortingParams *schemas.SortingQuery) *gorm.DB {
465
+ if pagingParams.NextPageToken != "" {
466
+ sortColumn := sortingParams.Sort
467
+ if sortColumn == "name" {
468
+ sortColumn = "name collate numeric"
469
+ } else {
470
+ sortColumn = utils.CamelToSnake(sortingParams.Sort)
471
+ }
472
+
473
+ tokenValue, err := base64.StdEncoding.DecodeString(pagingParams.NextPageToken)
474
+ if err == nil {
475
+ if sortingParams.Order == "asc" {
476
+ return query.Where(fmt.Sprintf("%s > ?", sortColumn), string(tokenValue))
477
+ } else {
478
+ return query.Where(fmt.Sprintf("%s < ?", sortColumn), string(tokenValue))
479
+ }
480
+ }
481
+ }
482
+ return query
483
+ }
484
+
485
+ func getOrder(sortingParams schemas.SortingQuery) string {
486
+ sortColumn := utils.CamelToSnake(sortingParams.Sort)
487
+ if sortingParams.Sort == "name" {
488
+ sortColumn = "name collate numeric"
489
+ }
490
+
491
+ return fmt.Sprintf("%s %s", sortColumn, strings.ToUpper(sortingParams.Order))
492
+ }
493
+
494
+ func chunk(ctx context.Context, tgClient *telegram.Client, part *types.Part, offset int64, limit int64) ([]byte, error) {
495
+
496
+ req := &tg.UploadGetFileRequest{
497
+ Offset: offset,
498
+ Limit: int(limit),
499
+ Location: part.Location,
500
+ }
501
+
502
+ r, err := tgClient.API().UploadGetFile(ctx, req)
503
+
504
+ if err != nil {
505
+ return nil, err
506
+ }
507
+
508
+ switch result := r.(type) {
509
+ case *tg.UploadFile:
510
+ return result.Bytes, nil
511
+ default:
512
+ return nil, fmt.Errorf("unexpected type %T", r)
513
+ }
514
+ }
515
+
516
+ func totalParts(start, end, chunkSize int64) int {
517
+
518
+ totalBytes := end - start + 1
519
+ parts := totalBytes / chunkSize
520
+
521
+ if totalBytes%chunkSize != 0 {
522
+ parts++
523
+ }
524
+
525
+ return int(parts)
526
+ }
527
+
528
+ func streamFilePart(ctx context.Context, tgClient *telegram.Client, writer *io.PipeWriter, part *types.Part, start, end, chunkSize int64) error {
529
+
530
+ offset := start - (start % chunkSize)
531
+ firstPartCut := start - offset
532
+ lastPartCut := (end % chunkSize) + 1
533
+
534
+ partCount := totalParts(start, end, chunkSize)
535
+
536
+ currentPart := 1
537
+
538
+ for {
539
+ r, _ := chunk(ctx, tgClient, part, offset, chunkSize)
540
+
541
+ if len(r) == 0 {
542
+ break
543
+ } else if partCount == 1 {
544
+ r = r[firstPartCut:lastPartCut]
545
+
546
+ } else if currentPart == 1 {
547
+ r = r[firstPartCut:]
548
+
549
+ } else if currentPart == partCount {
550
+ r = r[:lastPartCut]
551
+
552
+ }
553
+
554
+ writer.Write(r)
555
+
556
+ currentPart++
557
+
558
+ offset += chunkSize
559
+
560
+ if currentPart > partCount {
561
+ break
562
+ }
563
+
564
+ }
565
+
566
+ return nil
567
+ }
568
+
569
+ func rangedParts(parts []types.Part, start, end int64) []types.Part {
570
+
571
+ chunkSize := parts[0].Size
572
+
573
+ startPartNumber := utils.Max(int64(math.Ceil(float64(start)/float64(chunkSize)))-1, 0)
574
+
575
+ endPartNumber := int64(math.Ceil(float64(end) / float64(chunkSize)))
576
+
577
+ partsToDownload := parts[startPartNumber:endPartNumber]
578
+ partsToDownload[0].Start = start % chunkSize
579
+ partsToDownload[len(partsToDownload)-1].End = end % chunkSize
580
+
581
+ return partsToDownload
582
+ }
services/upload.service.go ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package services
2
+
3
+ import (
4
+ "context"
5
+ "errors"
6
+ "fmt"
7
+ "net/http"
8
+
9
+ "github.com/divyam234/teldrive/cache"
10
+ "github.com/divyam234/teldrive/schemas"
11
+ "github.com/divyam234/teldrive/utils"
12
+
13
+ "github.com/divyam234/teldrive/types"
14
+
15
+ "github.com/divyam234/teldrive/models"
16
+ "github.com/gin-gonic/gin"
17
+ "github.com/gotd/td/telegram/message"
18
+ "github.com/gotd/td/telegram/uploader"
19
+ "github.com/gotd/td/tg"
20
+ "gorm.io/gorm"
21
+ )
22
+
23
+ type UploadService struct {
24
+ Db *gorm.DB
25
+ ChannelID int64
26
+ }
27
+
28
+ func (us *UploadService) GetUploadFileById(c *gin.Context) (*schemas.UploadOut, *types.AppError) {
29
+ uploadId := c.Param("id")
30
+ parts := []schemas.UploadPartOut{}
31
+ if err := us.Db.Model(&models.Upload{}).Order("part_no").Where("upload_id = ?", uploadId).Find(&parts).Error; err != nil {
32
+ return nil, &types.AppError{Error: errors.New("failed to fetch from db"), Code: http.StatusInternalServerError}
33
+ }
34
+
35
+ return &schemas.UploadOut{Parts: parts}, nil
36
+ }
37
+
38
+ func (us *UploadService) DeleteUploadFile(c *gin.Context) *types.AppError {
39
+ uploadId := c.Param("id")
40
+ if err := us.Db.Where("upload_id = ?", uploadId).Delete(&models.Upload{}).Error; err != nil {
41
+ return &types.AppError{Error: errors.New("failed to delete upload"), Code: http.StatusInternalServerError}
42
+ }
43
+
44
+ return nil
45
+ }
46
+
47
+ func (us *UploadService) UploadFile(c *gin.Context) (*schemas.UploadPartOut, *types.AppError) {
48
+
49
+ var uploadQuery schemas.UploadQuery
50
+
51
+ uploadQuery.PartNo = 1
52
+ uploadQuery.TotalParts = 1
53
+
54
+ if err := c.ShouldBindQuery(&uploadQuery); err != nil {
55
+ return nil, &types.AppError{Error: err, Code: http.StatusBadRequest}
56
+ }
57
+
58
+ if uploadQuery.Filename == "" {
59
+ return nil, &types.AppError{Error: errors.New("filename missing"), Code: http.StatusBadRequest}
60
+ }
61
+
62
+ uploadId := c.Param("id")
63
+
64
+ var uploadPart []models.Upload
65
+
66
+ us.Db.Model(&models.Upload{}).Where("upload_id = ?", uploadId).Where("part_no = ?", uploadQuery.PartNo).Find(&uploadPart)
67
+
68
+ if len(uploadPart) == 1 {
69
+ out := mapSchema(&uploadPart[0])
70
+ return out, nil
71
+ }
72
+
73
+ client, idx := utils.GetUploadClient(c)
74
+
75
+ file := c.Request.Body
76
+
77
+ fileSize := c.Request.ContentLength
78
+
79
+ fileName := uploadQuery.Filename
80
+
81
+ var msgId int
82
+
83
+ ctx := context.Background()
84
+
85
+ ctx, cancel := context.WithCancel(ctx)
86
+
87
+ defer func() {
88
+ if idx != -1 {
89
+ utils.GetClientWorkload().Dec(idx)
90
+ }
91
+ cancel()
92
+ }()
93
+
94
+ err := client.Run(ctx, func(ctx context.Context) error {
95
+
96
+ api := client.API()
97
+
98
+ u := uploader.NewUploader(api).WithThreads(8).WithPartSize(512 * 1024)
99
+
100
+ upload, err := u.Upload(c, uploader.NewUpload(fileName, file, fileSize))
101
+
102
+ if err != nil {
103
+ return err
104
+ }
105
+
106
+ document := message.UploadedDocument(upload).Filename(fileName).ForceFile(true)
107
+
108
+ res, err := cache.CachedFunction(utils.GetChannelById, fmt.Sprintf("channels:%d", us.ChannelID))(c, client.API(), us.ChannelID)
109
+
110
+ if err != nil {
111
+ return err
112
+ }
113
+
114
+ channel := res.(*tg.Channel)
115
+
116
+ sender := message.NewSender(client.API())
117
+
118
+ target := sender.To(&tg.InputPeerChannel{ChannelID: channel.ID, AccessHash: channel.AccessHash})
119
+
120
+ res, err = target.Media(c, document)
121
+
122
+ if err != nil {
123
+ return err
124
+ }
125
+
126
+ updates := res.(*tg.Updates)
127
+
128
+ msgId = updates.Updates[0].(*tg.UpdateMessageID).ID
129
+
130
+ return nil
131
+ })
132
+
133
+ if err != nil {
134
+ return nil, &types.AppError{Error: err, Code: http.StatusInternalServerError}
135
+ }
136
+
137
+ partUpload := &models.Upload{
138
+ Name: fileName,
139
+ UploadId: uploadId,
140
+ PartId: msgId,
141
+ ChannelID: us.ChannelID,
142
+ Size: fileSize,
143
+ PartNo: uploadQuery.PartNo,
144
+ TotalParts: uploadQuery.TotalParts,
145
+ }
146
+
147
+ if err := us.Db.Create(partUpload).Error; err != nil {
148
+ return nil, &types.AppError{Error: errors.New("failed to upload part"), Code: http.StatusInternalServerError}
149
+ }
150
+
151
+ out := mapSchema(partUpload)
152
+
153
+ return out, nil
154
+ }
155
+
156
+ func mapSchema(in *models.Upload) *schemas.UploadPartOut {
157
+ out := &schemas.UploadPartOut{
158
+ ID: in.ID,
159
+ Name: in.Name,
160
+ PartId: in.PartId,
161
+ ChannelID: in.ChannelID,
162
+ PartNo: in.PartNo,
163
+ TotalParts: in.TotalParts,
164
+ Size: in.Size,
165
+ }
166
+ return out
167
+ }
services/user.service.go ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package services
2
+
3
+ import (
4
+ "bytes"
5
+ "context"
6
+ "fmt"
7
+ "net/http"
8
+ "strconv"
9
+
10
+ "github.com/divyam234/teldrive/types"
11
+ "github.com/divyam234/teldrive/utils"
12
+ "github.com/gotd/td/telegram"
13
+ "github.com/gotd/td/tg"
14
+
15
+ "github.com/gin-gonic/gin"
16
+ )
17
+
18
+ type UserService struct {
19
+ }
20
+
21
+ func getChunk(ctx context.Context, tgClient *telegram.Client, location tg.InputFileLocationClass, offset int64, limit int) ([]byte, error) {
22
+
23
+ req := &tg.UploadGetFileRequest{
24
+ Offset: offset,
25
+ Limit: int(limit),
26
+ Location: location,
27
+ }
28
+
29
+ r, err := tgClient.API().UploadGetFile(ctx, req)
30
+
31
+ if err != nil {
32
+ return nil, err
33
+ }
34
+
35
+ switch result := r.(type) {
36
+ case *tg.UploadFile:
37
+ return result.Bytes, nil
38
+ default:
39
+ return nil, fmt.Errorf("unexpected type %T", r)
40
+ }
41
+ }
42
+
43
+ func iterContent(ctx context.Context, tgClient *telegram.Client, location tg.InputFileLocationClass) (*bytes.Buffer, error) {
44
+ offset := int64(0)
45
+ limit := 1024 * 1024
46
+ buff := &bytes.Buffer{}
47
+ for {
48
+ r, err := getChunk(ctx, tgClient, location, offset, limit)
49
+ if err != nil {
50
+ return buff, err
51
+ }
52
+ if len(r) == 0 {
53
+ break
54
+ }
55
+ buff.Write(r)
56
+ offset += int64(limit)
57
+ }
58
+ return buff, nil
59
+ }
60
+
61
+ func (us *UserService) GetProfilePhoto(c *gin.Context) {
62
+ val, _ := c.Get("jwtUser")
63
+ jwtUser := val.(*types.JWTClaims)
64
+ userId, _ := strconv.ParseInt(jwtUser.Subject, 10, 64)
65
+ client, _ := utils.GetAuthClient(c, jwtUser.TgSession, userId)
66
+
67
+ err := client.Run(c, func(ctx context.Context) error {
68
+ self, err := client.Self(c)
69
+ if err != nil {
70
+ return err
71
+ }
72
+ peer := self.AsInputPeer()
73
+ photo, _ := self.Photo.AsNotEmpty()
74
+ location := &tg.InputPeerPhotoFileLocation{Big: false, Peer: peer, PhotoID: photo.PhotoID}
75
+ buff, err := iterContent(c, client, location)
76
+ if err != nil {
77
+ return err
78
+ }
79
+ content := buff.Bytes()
80
+ c.Writer.Header().Set("Content-Type", "image/jpeg")
81
+ c.Writer.Header().Set("Cache-Control", "public, max-age=86400")
82
+ c.Writer.Header().Set("Content-Length", strconv.Itoa(len(content)))
83
+ c.Writer.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", "profile.jpeg"))
84
+ c.Writer.Write(content)
85
+ return nil
86
+ })
87
+ if err != nil {
88
+ http.Error(c.Writer, err.Error(), http.StatusBadRequest)
89
+ return
90
+ }
91
+ }
types/main.go ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package types
2
+
3
+ import (
4
+ "github.com/go-jose/go-jose/v3/jwt"
5
+ "github.com/gotd/td/tg"
6
+ )
7
+
8
+ type AppError struct {
9
+ Error error
10
+ Code int
11
+ }
12
+
13
+ type Part struct {
14
+ Location *tg.InputDocumentFileLocation
15
+ Size int64
16
+ Start int64
17
+ End int64
18
+ }
19
+
20
+ type JWTClaims struct {
21
+ jwt.Claims
22
+ TgSession string `json:"tgSession"`
23
+ Name string `json:"name"`
24
+ UserName string `json:"userName"`
25
+ Bot bool `json:"bot"`
26
+ IsPremium bool `json:"isPremium"`
27
+ }
28
+
29
+ type TgSession struct {
30
+ Sesssion string `json:"session"`
31
+ UserID int64 `json:"userId"`
32
+ Bot bool `json:"bot"`
33
+ UserName string `json:"userName"`
34
+ Name string `json:"name"`
35
+ IsPremium bool `json:"isPremium"`
36
+ }
37
+
38
+ type Session struct {
39
+ Name string `json:"name"`
40
+ UserName string `json:"userName"`
41
+ IsPremium bool `json:"isPremium"`
42
+ Expires string `json:"expires"`
43
+ }
utils/auth/jwe.go ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package auth
2
+
3
+ import (
4
+ "encoding/json"
5
+ "os"
6
+
7
+ "github.com/divyam234/teldrive/types"
8
+ "github.com/go-jose/go-jose/v3"
9
+ )
10
+
11
+ func Encode(payload *types.JWTClaims) (string, error) {
12
+
13
+ rcpt := jose.Recipient{
14
+ Algorithm: jose.PBES2_HS256_A128KW,
15
+ Key: os.Getenv("JWT_SECRET"),
16
+ }
17
+
18
+ enc, err := jose.NewEncrypter(jose.A128CBC_HS256, rcpt, nil)
19
+
20
+ if err != nil {
21
+ return "", err
22
+ }
23
+
24
+ jwt, _ := json.Marshal(payload)
25
+
26
+ jweObject, err := enc.Encrypt(jwt)
27
+
28
+ if err != nil {
29
+ return "", err
30
+ }
31
+
32
+ jweToken, err := jweObject.CompactSerialize()
33
+
34
+ if err != nil {
35
+ return "", err
36
+ }
37
+ return jweToken, nil
38
+ }
39
+
40
+ func Decode(token string) (*types.JWTClaims, error) {
41
+ jwe, err := jose.ParseEncrypted(token)
42
+ if err != nil {
43
+ return nil, err
44
+ }
45
+
46
+ decryptedData, err := jwe.Decrypt(os.Getenv("JWT_SECRET"))
47
+
48
+ if err != nil {
49
+ return nil, err
50
+ }
51
+
52
+ jwtToken := &types.JWTClaims{}
53
+
54
+ err = json.Unmarshal(decryptedData, jwtToken)
55
+
56
+ if err != nil {
57
+ return nil, err
58
+ }
59
+
60
+ return jwtToken, nil
61
+
62
+ }
utils/config.go ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package utils
2
+
3
+ import (
4
+ "path/filepath"
5
+
6
+ "github.com/joho/godotenv"
7
+ "github.com/kelseyhightower/envconfig"
8
+ )
9
+
10
+ type MultiToken string
11
+
12
+ type Config struct {
13
+ AppId int `envconfig:"APP_ID" required:"true"`
14
+ AppHash string `envconfig:"APP_HASH" required:"true"`
15
+ ChannelID int64 `envconfig:"CHANNEL_ID" required:"true"`
16
+ JwtSecret string `envconfig:"JWT_SECRET" required:"true"`
17
+ MultiClient bool `envconfig:"MULTI_CLIENT" default:"false"`
18
+ Https bool `envconfig:"HTTPS" default:"false"`
19
+ CookieSameSite bool `envconfig:"COOKIE_SAME_SITE" default:"true"`
20
+ DatabaseUrl string `envconfig:"DATABASE_URL" required:"true"`
21
+ RateLimit bool `envconfig:"RATE_LIMIT" default:"true"`
22
+ TgClientDeviceModel string `envconfig:"TG_CLIENT_DEVICE_MODEL" required:"true"`
23
+ TgClientSystemVersion string `envconfig:"TG_CLIENT_SYSTEM_VERSION" default:"Win32"`
24
+ TgClientAppVersion string `envconfig:"TG_CLIENT_APP_VERSION" default:"2.1.9 K"`
25
+ TgClientLangCode string `envconfig:"TG_CLIENT_LANG_CODE" default:"en"`
26
+ TgClientSystemLangCode string `envconfig:"TG_CLIENT_SYSTEM_LANG_CODE" default:"en"`
27
+ TgClientLangPack string `envconfig:"TG_CLIENT_LANG_PACK" default:"webk"`
28
+ RunMigrations bool `envconfig:"RUN_MIGRATIONS" default:"true"`
29
+ Port int `envconfig:"PORT" default:"8080"`
30
+ ExecDir string
31
+ }
32
+
33
+ var config Config
34
+
35
+ func InitConfig() {
36
+
37
+ execDir := getExecutableDir()
38
+
39
+ godotenv.Load(filepath.Join(execDir, ".env"))
40
+
41
+ godotenv.Load(filepath.Join(execDir, "teldrive.env"))
42
+ err := envconfig.Process("", &config)
43
+ if err != nil {
44
+ panic(err)
45
+ }
46
+ config.ExecDir = execDir
47
+ }
48
+
49
+ func GetConfig() *Config {
50
+ return &config
51
+ }
utils/cron/cron.go ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package cron
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+
7
+ "github.com/divyam234/teldrive/cache"
8
+ "github.com/divyam234/teldrive/database"
9
+ "github.com/divyam234/teldrive/models"
10
+ "github.com/divyam234/teldrive/utils"
11
+ "github.com/gotd/td/tg"
12
+ )
13
+
14
+ type Result struct {
15
+ ID string
16
+ Parts models.Parts
17
+ TgSession string
18
+ UserId int64
19
+ ChannelId int64
20
+ }
21
+
22
+ func deleteTGMessage(ctx context.Context, client *tg.Client, result Result) error {
23
+
24
+ ids := make([]int, len(result.Parts))
25
+
26
+ for i, part := range result.Parts {
27
+ ids[i] = int(part.ID)
28
+ }
29
+
30
+ res, err := cache.CachedFunction(utils.GetChannelById, fmt.Sprintf("channels:%d", result.ChannelId))(ctx, client, result.ChannelId)
31
+
32
+ if err != nil {
33
+ return err
34
+ }
35
+
36
+ channel := res.(*tg.Channel)
37
+
38
+ messageDeleteRequest := tg.ChannelsDeleteMessagesRequest{Channel: &tg.InputChannel{ChannelID: result.ChannelId, AccessHash: channel.AccessHash},
39
+ ID: ids}
40
+
41
+ _, err = client.ChannelsDeleteMessages(ctx, &messageDeleteRequest)
42
+ if err != nil {
43
+ return err
44
+ }
45
+ return nil
46
+ }
47
+
48
+ func FilesDeleteJob() {
49
+ db := database.DB
50
+ ctx := context.Background()
51
+ var results []Result
52
+ if err := db.Model(&models.File{}).Select("files.id", "files.parts", "files.user_id", "files.channel_id", "u.tg_session").
53
+ Joins("left join teldrive.users as u on u.user_id = files.user_id").
54
+ Where("status = ?", "pending_deletion").Scan(&results).Error; err != nil {
55
+ return
56
+ }
57
+
58
+ for _, file := range results {
59
+ client, err := utils.GetAuthClient(ctx, file.TgSession, file.UserId)
60
+ if err != nil {
61
+ break
62
+ }
63
+ err = client.Run(ctx, func(ctx context.Context) error {
64
+ err = deleteTGMessage(ctx, client.API(), file)
65
+ return err
66
+ })
67
+
68
+ if err == nil {
69
+ db.Where("id = ?", file.ID).Delete(&models.File{})
70
+ }
71
+ }
72
+ }
utils/logger.go ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package utils
2
+
3
+ import (
4
+ "os"
5
+
6
+ "go.uber.org/zap"
7
+ "go.uber.org/zap/zapcore"
8
+ )
9
+
10
+ var Logger *zap.Logger
11
+
12
+ func InitializeLogger() {
13
+ config := zap.NewProductionEncoderConfig()
14
+ config.EncodeTime = zapcore.ISO8601TimeEncoder
15
+ consoleEncoder := zapcore.NewConsoleEncoder(config)
16
+ defaultLogLevel := zapcore.DebugLevel
17
+ core := zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), defaultLogLevel)
18
+ Logger = zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
19
+ }
utils/main.go ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package utils
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "os"
7
+ "path/filepath"
8
+ "regexp"
9
+ "strings"
10
+ "time"
11
+
12
+ "reflect"
13
+
14
+ "github.com/gotd/td/tg"
15
+ "golang.org/x/exp/constraints"
16
+
17
+ "unicode"
18
+ )
19
+
20
+ func Max[T constraints.Ordered](a, b T) T {
21
+ if a > b {
22
+ return a
23
+ }
24
+ return b
25
+ }
26
+
27
+ func CamelToPascalCase(input string) string {
28
+ var result strings.Builder
29
+ upperNext := true
30
+
31
+ for _, char := range input {
32
+ if unicode.IsLetter(char) || unicode.IsDigit(char) {
33
+ if upperNext {
34
+ result.WriteRune(unicode.ToUpper(char))
35
+ upperNext = false
36
+ } else {
37
+ result.WriteRune(char)
38
+ }
39
+ } else {
40
+ upperNext = true
41
+ }
42
+ }
43
+
44
+ return result.String()
45
+ }
46
+
47
+ func CamelToSnake(input string) string {
48
+ re := regexp.MustCompile("([a-z0-9])([A-Z])")
49
+ snake := re.ReplaceAllString(input, "${1}_${2}")
50
+ return strings.ToLower(snake)
51
+ }
52
+
53
+ func GetField(v interface{}, field string) string {
54
+ r := reflect.ValueOf(v)
55
+ f := reflect.Indirect(r).FieldByName(field)
56
+ fieldValue := f.Interface()
57
+
58
+ switch v := fieldValue.(type) {
59
+ case string:
60
+ return v
61
+ case time.Time:
62
+ return v.Format(time.RFC3339)
63
+ default:
64
+ return ""
65
+ }
66
+ }
67
+
68
+ func GetChannelById(ctx context.Context, client *tg.Client, channelID int64) (*tg.Channel, error) {
69
+ inputChannel := &tg.InputChannel{
70
+ ChannelID: channelID,
71
+ AccessHash: 0,
72
+ }
73
+ channels, err := client.ChannelsGetChannels(ctx, []tg.InputChannelClass{inputChannel})
74
+
75
+ if err != nil {
76
+ return nil, fmt.Errorf("failed to fetch channel: %w", err)
77
+ }
78
+
79
+ if len(channels.GetChats()) == 0 {
80
+ return nil, fmt.Errorf("no channels found")
81
+ }
82
+
83
+ channel := channels.GetChats()[0].(*tg.Channel)
84
+ return channel, nil
85
+ }
86
+
87
+ func BoolPointer(b bool) *bool {
88
+ return &b
89
+ }
90
+
91
+ func IntPointer(b int) *int {
92
+ return &b
93
+ }
94
+
95
+ func PathExists(path string) (bool, error) {
96
+ _, err := os.Stat(path)
97
+ if err == nil {
98
+ return true, nil
99
+ }
100
+ if os.IsNotExist(err) {
101
+ return false, nil
102
+ }
103
+ return false, err
104
+ }
105
+
106
+ func getExecutableDir() string {
107
+
108
+ path, _ := os.Executable()
109
+
110
+ executableDir := filepath.Dir(path)
111
+
112
+ return executableDir
113
+ }
utils/tgclient.go ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package utils
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "os"
7
+ "path/filepath"
8
+ "sort"
9
+ "strconv"
10
+ "strings"
11
+ "sync"
12
+ "time"
13
+
14
+ "github.com/cenkalti/backoff/v4"
15
+ "github.com/divyam234/teldrive/types"
16
+ "github.com/gin-gonic/gin"
17
+ "github.com/gotd/contrib/bg"
18
+ "github.com/gotd/contrib/middleware/floodwait"
19
+ "github.com/gotd/contrib/middleware/ratelimit"
20
+ tdclock "github.com/gotd/td/clock"
21
+ "github.com/gotd/td/session"
22
+ "github.com/gotd/td/telegram"
23
+ "github.com/pkg/errors"
24
+ "go.uber.org/zap"
25
+ "golang.org/x/time/rate"
26
+ )
27
+
28
+ var clients map[int64]*telegram.Client
29
+
30
+ type Workload struct {
31
+ mu sync.Mutex
32
+ workloads map[int]int
33
+ }
34
+
35
+ func (w *Workload) Set(key int, value int) {
36
+ w.mu.Lock()
37
+ defer w.mu.Unlock()
38
+ w.workloads[key] = value
39
+ }
40
+
41
+ func (w *Workload) Get(key int) int {
42
+ w.mu.Lock()
43
+ defer w.mu.Unlock()
44
+ return w.workloads[key]
45
+ }
46
+
47
+ func (w *Workload) Inc(key int) {
48
+ w.mu.Lock()
49
+ defer w.mu.Unlock()
50
+ w.workloads[key]++
51
+ }
52
+
53
+ func (w *Workload) Dec(key int) {
54
+ w.mu.Lock()
55
+ defer w.mu.Unlock()
56
+ w.workloads[key]--
57
+ }
58
+
59
+ func (w *Workload) GetMinIndex() int {
60
+ w.mu.Lock()
61
+ defer w.mu.Unlock()
62
+ smallest := w.workloads[0]
63
+ idx := 0
64
+ for i, workload := range clientWorkload.workloads {
65
+ if workload < smallest {
66
+ smallest = workload
67
+ idx = i
68
+ }
69
+ }
70
+ return idx
71
+ }
72
+
73
+ var clientWorkload *Workload
74
+
75
+ func GetClientWorkload() *Workload {
76
+ return clientWorkload
77
+ }
78
+
79
+ func getDeviceConfig() telegram.DeviceConfig {
80
+ appConfig := GetConfig()
81
+ config := telegram.DeviceConfig{
82
+ DeviceModel: appConfig.TgClientDeviceModel,
83
+ SystemVersion: appConfig.TgClientSystemVersion,
84
+ AppVersion: appConfig.TgClientAppVersion,
85
+ SystemLangCode: appConfig.TgClientSystemLangCode,
86
+ LangPack: appConfig.TgClientLangPack,
87
+ LangCode: appConfig.TgClientLangCode,
88
+ }
89
+ return config
90
+ }
91
+
92
+ func reconnectionBackoff() backoff.BackOff {
93
+ _clock := tdclock.System
94
+ b := backoff.NewExponentialBackOff()
95
+ b.Multiplier = 1.1
96
+ b.MaxElapsedTime = time.Duration(120) * time.Second
97
+ b.Clock = _clock
98
+ return b
99
+ }
100
+
101
+ func GetBotClient(clientName string) *telegram.Client {
102
+
103
+ config := GetConfig()
104
+ sessionStorage := &telegram.FileSessionStorage{
105
+ Path: filepath.Join(config.ExecDir, "sessions", clientName+".json"),
106
+ }
107
+
108
+ middlewares := []telegram.Middleware{floodwait.NewSimpleWaiter()}
109
+
110
+ if config.RateLimit {
111
+ middlewares = append(middlewares, ratelimit.New(rate.Every(time.Millisecond*100), 5))
112
+ }
113
+
114
+ options := telegram.Options{
115
+ SessionStorage: sessionStorage,
116
+ Middlewares: middlewares,
117
+ ReconnectionBackoff: reconnectionBackoff,
118
+ RetryInterval: 5 * time.Second,
119
+ MaxRetries: 5,
120
+ Device: getDeviceConfig(),
121
+ Clock: tdclock.System,
122
+ }
123
+
124
+ client := telegram.NewClient(config.AppId, config.AppHash, options)
125
+
126
+ return client
127
+
128
+ }
129
+
130
+ func GetAuthClient(ctx context.Context, sessionStr string, userId int64) (*telegram.Client, error) {
131
+
132
+ data, err := session.TelethonSession(sessionStr)
133
+
134
+ if err != nil {
135
+ return nil, err
136
+ }
137
+
138
+ var (
139
+ storage = new(session.StorageMemory)
140
+ loader = session.Loader{Storage: storage}
141
+ )
142
+
143
+ if err := loader.Save(ctx, data); err != nil {
144
+ return nil, err
145
+ }
146
+
147
+ middlewares := []telegram.Middleware{floodwait.NewSimpleWaiter()}
148
+
149
+ if config.RateLimit {
150
+ middlewares = append(middlewares, ratelimit.New(rate.Every(time.Millisecond*100), 5))
151
+ }
152
+
153
+ client := telegram.NewClient(config.AppId, config.AppHash, telegram.Options{
154
+ SessionStorage: storage,
155
+ Middlewares: middlewares,
156
+ ReconnectionBackoff: reconnectionBackoff,
157
+ RetryInterval: 5 * time.Second,
158
+ MaxRetries: 5,
159
+ Device: getDeviceConfig(),
160
+ Clock: tdclock.System,
161
+ })
162
+
163
+ return client, nil
164
+ }
165
+
166
+ func StartNonAuthClient(handler telegram.UpdateHandler, storage telegram.SessionStorage) (*telegram.Client, bg.StopFunc, error) {
167
+ middlewares := []telegram.Middleware{}
168
+ if config.RateLimit {
169
+ middlewares = append(middlewares, ratelimit.New(rate.Every(time.Millisecond*100), 5))
170
+ }
171
+ client := telegram.NewClient(config.AppId, config.AppHash, telegram.Options{
172
+ SessionStorage: storage,
173
+ Middlewares: middlewares,
174
+ Device: getDeviceConfig(),
175
+ UpdateHandler: handler,
176
+ })
177
+
178
+ stop, err := bg.Connect(client)
179
+
180
+ if err != nil {
181
+ return nil, nil, err
182
+ }
183
+
184
+ return client, stop, nil
185
+ }
186
+
187
+ func startBotClient(ctx context.Context, client *telegram.Client, token string) (bg.StopFunc, error) {
188
+
189
+ stop, err := bg.Connect(client)
190
+
191
+ if err != nil {
192
+ return nil, errors.Wrap(err, "failed to start client")
193
+ }
194
+
195
+ tguser, err := client.Self(ctx)
196
+
197
+ if err != nil {
198
+
199
+ if _, err := client.Auth().Bot(ctx, token); err != nil {
200
+ return nil, err
201
+ }
202
+ tguser, _ = client.Self(ctx)
203
+ }
204
+
205
+ Logger.Info("started Client", zap.String("user", tguser.Username))
206
+ return stop, nil
207
+ }
208
+
209
+ func startAuthClient(c *gin.Context, client *telegram.Client) (bg.StopFunc, error) {
210
+ stop, err := bg.Connect(client)
211
+
212
+ if err != nil {
213
+ return nil, err
214
+ }
215
+
216
+ tguser, err := client.Self(c)
217
+
218
+ if err != nil {
219
+ return nil, err
220
+ }
221
+
222
+ Logger.Info("started Client", zap.String("user", tguser.Username))
223
+
224
+ clients[tguser.GetID()] = client
225
+
226
+ return stop, nil
227
+ }
228
+
229
+ func InitBotClients() {
230
+
231
+ ctx := context.Background()
232
+
233
+ clients = make(map[int64]*telegram.Client)
234
+
235
+ clientWorkload = &Workload{workloads: make(map[int]int)}
236
+
237
+ if config.MultiClient {
238
+
239
+ if err := os.MkdirAll(filepath.Join(config.ExecDir, "sessions"), 0700); err != nil {
240
+ return
241
+ }
242
+
243
+ var keysToSort []string
244
+
245
+ for _, e := range os.Environ() {
246
+ if strings.HasPrefix(e, "MULTI_TOKEN") {
247
+ if i := strings.Index(e, "="); i >= 0 {
248
+ keysToSort = append(keysToSort, e[:i])
249
+ }
250
+ }
251
+ }
252
+
253
+ sort.Strings(keysToSort)
254
+
255
+ for idx, key := range keysToSort {
256
+ client := GetBotClient(fmt.Sprintf("client%d", idx))
257
+ clientWorkload.Set(idx, 0)
258
+ clients[int64(idx)] = client
259
+ go func(k string) {
260
+ startBotClient(ctx, client, os.Getenv(k))
261
+ }(key)
262
+ }
263
+
264
+ }
265
+ }
266
+
267
+ func GetUploadClient(c *gin.Context) (*telegram.Client, int) {
268
+ if config.MultiClient {
269
+ idx := clientWorkload.GetMinIndex()
270
+ clientWorkload.Inc(idx)
271
+ return GetBotClient(fmt.Sprintf("client%d", idx)), idx
272
+ } else {
273
+ val, _ := c.Get("jwtUser")
274
+ jwtUser := val.(*types.JWTClaims)
275
+ userId, _ := strconv.ParseInt(jwtUser.Subject, 10, 64)
276
+ client, _ := GetAuthClient(c, jwtUser.TgSession, userId)
277
+ return client, -1
278
+ }
279
+ }
280
+
281
+ func GetDownloadClient(c *gin.Context) (*telegram.Client, int) {
282
+ if config.MultiClient {
283
+ idx := clientWorkload.GetMinIndex()
284
+ clientWorkload.Inc(idx)
285
+ return clients[int64(idx)], idx
286
+ } else {
287
+ val, _ := c.Get("jwtUser")
288
+ jwtUser := val.(*types.JWTClaims)
289
+ userId, _ := strconv.ParseInt(jwtUser.Subject, 10, 64)
290
+ if client, ok := clients[userId]; ok {
291
+ return client, -1
292
+ }
293
+ client, _ := GetAuthClient(c, jwtUser.TgSession, userId)
294
+ startAuthClient(c, client)
295
+ return client, -1
296
+ }
297
+ }