mirror of
https://github.com/grassrootseconomics/farmstar-survey-backend.git
synced 2024-12-04 15:36:48 +01:00
init: pilot demo
This commit is contained in:
commit
da4c45819d
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@ -0,0 +1 @@
|
||||
pb_data
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
pb_data/
|
||||
bin/
|
||||
.env
|
28
Dockerfile
Normal file
28
Dockerfile
Normal file
@ -0,0 +1,28 @@
|
||||
FROM golang:1-bookworm as build
|
||||
|
||||
ENV CGO_ENABLED=0
|
||||
ENV GOOS=linux
|
||||
ENV GOARCH=amd64
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
COPY go.* .
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
RUN go build -o farmstar-survey-backend -ldflags="-s -w" cmd/farmstar/*.go
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
WORKDIR /service
|
||||
|
||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=build /build/farmstar-survey-backend .
|
||||
COPY LICENSE .
|
||||
COPY config.toml .
|
||||
|
||||
EXPOSE 8090
|
||||
|
||||
CMD ["/service/farmstar-survey-backend", "serve", "--http=0.0.0.0:8090"]
|
661
LICENSE
Normal file
661
LICENSE
Normal file
@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
160
cmd/farmstar/init.go
Normal file
160
cmd/farmstar/init.go
Normal file
@ -0,0 +1,160 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/grassrootseconomics/farmstar-survey-backend/internal/worker"
|
||||
"github.com/grassrootseconomics/farmstar-survey-backend/pkg/custodial"
|
||||
"github.com/grassrootseconomics/farmstar-survey-backend/pkg/telegram"
|
||||
"github.com/grassrootseconomics/farmstar-survey-backend/pkg/ussd"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/kamikazechaser/africastalking"
|
||||
"github.com/knadh/koanf/parsers/toml"
|
||||
"github.com/knadh/koanf/providers/env"
|
||||
"github.com/knadh/koanf/providers/file"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/plugins/migratecmd"
|
||||
"github.com/riverqueue/river"
|
||||
"github.com/riverqueue/river/riverdriver/riverpgxv5"
|
||||
"github.com/riverqueue/river/rivermigrate"
|
||||
)
|
||||
|
||||
func initLogger() *slog.Logger {
|
||||
return slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
||||
AddSource: true,
|
||||
Level: slog.LevelError,
|
||||
}))
|
||||
}
|
||||
|
||||
func initConfig() *koanf.Koanf {
|
||||
var (
|
||||
ko = koanf.New(".")
|
||||
)
|
||||
|
||||
confFile := file.Provider(confFlag)
|
||||
if err := ko.Load(confFile, toml.Parser()); err != nil {
|
||||
lo.Error("could not parse configuration file", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := ko.Load(env.Provider("FARMSTAR_", ".", func(s string) string {
|
||||
return strings.ReplaceAll(strings.ToLower(
|
||||
strings.TrimPrefix(s, "FARMSTAR_")), "__", ".")
|
||||
}), nil); err != nil {
|
||||
lo.Error("could not override config from env vars", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return ko
|
||||
}
|
||||
|
||||
func initPocketbase() *pocketbase.PocketBase {
|
||||
app := pocketbase.New()
|
||||
|
||||
migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{
|
||||
Automigrate: true,
|
||||
})
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func initPostgres() *pgxpool.Pool {
|
||||
parsedConfig, err := pgxpool.ParseConfig(ko.MustString("postgres.dsn"))
|
||||
if err != nil {
|
||||
lo.Error("could not parse postgres dsn", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
dbPool, err := pgxpool.NewWithConfig(context.Background(), parsedConfig)
|
||||
if err != nil {
|
||||
lo.Error("could not create pgxpool", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return dbPool
|
||||
}
|
||||
|
||||
func initUSSDClient() *ussd.USSDClient {
|
||||
return ussd.New(ko.MustString("ussd.endpoint"))
|
||||
}
|
||||
|
||||
func initCustodialClient() *custodial.CustodialClient {
|
||||
return custodial.New(ko.MustString("custodial.endpoint"))
|
||||
}
|
||||
|
||||
func initTelegramClient() *telegram.TelegramClient {
|
||||
return telegram.New(ko.MustString("telegram.key"))
|
||||
}
|
||||
|
||||
func initATClient() *africastalking.AtClient {
|
||||
return africastalking.New(
|
||||
ko.MustString("at.key"),
|
||||
ko.MustString("at.username"),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
func initRiverQueueWWorker() *worker.Worker {
|
||||
ctx := context.Background()
|
||||
tx, err := postgresPool.Begin(ctx)
|
||||
if err != nil {
|
||||
lo.Error("could not begin pgx tx", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
migrator := rivermigrate.New(riverpgxv5.New(postgresPool), nil)
|
||||
_, err = migrator.MigrateTx(context.Background(), tx, rivermigrate.DirectionUp, nil)
|
||||
if err != nil {
|
||||
lo.Error("could not migrate river queue", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
lo.Error("could not commit pgx tx", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
workers := river.NewWorkers()
|
||||
if err := river.AddWorkerSafely(workers, &worker.RewardsWorker{
|
||||
Pocketbase: pocketbaseApp,
|
||||
USSDClient: ussdClient,
|
||||
CustodialClient: custodialClient,
|
||||
VaultAddress: ko.MustString("rewards.vault"),
|
||||
VoucherAddress: ko.MustString("rewards.voucher"),
|
||||
}); err != nil {
|
||||
lo.Error("could not bootstrap rewards worker", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := river.AddWorkerSafely(workers, &worker.SMSWorker{
|
||||
AtClient: atClient,
|
||||
}); err != nil {
|
||||
lo.Error("could not bootstrap SMS worker", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := river.AddWorkerSafely(workers, &worker.TelegramWorker{
|
||||
TgClient: tgClient,
|
||||
ChatID: ko.MustString("telegram.group"),
|
||||
}); err != nil {
|
||||
lo.Error("could not bootstrap tg worker", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
riverClient, err := river.NewClient(riverpgxv5.New(postgresPool), &river.Config{
|
||||
Logger: lo,
|
||||
Queues: map[string]river.QueueConfig{
|
||||
river.QueueDefault: {MaxWorkers: 10},
|
||||
},
|
||||
Workers: workers,
|
||||
})
|
||||
if err != nil {
|
||||
lo.Error("could not create a river client", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return worker.NewWorker(riverClient)
|
||||
}
|
74
cmd/farmstar/main.go
Normal file
74
cmd/farmstar/main.go
Normal file
@ -0,0 +1,74 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"github.com/grassrootseconomics/farmstar-survey-backend/internal/hooks"
|
||||
"github.com/grassrootseconomics/farmstar-survey-backend/internal/router"
|
||||
_ "github.com/grassrootseconomics/farmstar-survey-backend/migrations"
|
||||
"github.com/grassrootseconomics/farmstar-survey-backend/pkg/custodial"
|
||||
"github.com/grassrootseconomics/farmstar-survey-backend/pkg/telegram"
|
||||
"github.com/grassrootseconomics/farmstar-survey-backend/pkg/ussd"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/kamikazechaser/africastalking"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
)
|
||||
|
||||
var (
|
||||
confFlag string
|
||||
|
||||
lo *slog.Logger
|
||||
ko *koanf.Koanf
|
||||
|
||||
pocketbaseApp *pocketbase.PocketBase
|
||||
postgresPool *pgxpool.Pool
|
||||
ussdClient *ussd.USSDClient
|
||||
custodialClient *custodial.CustodialClient
|
||||
tgClient *telegram.TelegramClient
|
||||
atClient *africastalking.AtClient
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&confFlag, "config", "config.toml", "Config file location")
|
||||
flag.Parse()
|
||||
|
||||
lo = initLogger()
|
||||
ko = initConfig()
|
||||
}
|
||||
|
||||
func main() {
|
||||
pocketbaseApp = initPocketbase()
|
||||
postgresPool = initPostgres()
|
||||
|
||||
ussdClient = initUSSDClient()
|
||||
custodialClient = initCustodialClient()
|
||||
tgClient = initTelegramClient()
|
||||
atClient = initATClient()
|
||||
|
||||
worker := initRiverQueueWWorker()
|
||||
if err := worker.Start(); err != nil {
|
||||
lo.Error("could not start worker", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
hooks := hooks.NewHooks(hooks.Opts{
|
||||
PB: pocketbaseApp,
|
||||
Worker: worker,
|
||||
RedemptionVault: ko.MustString("rewards.number"),
|
||||
})
|
||||
hooks.Bootsrap()
|
||||
|
||||
router := router.NewRouter(router.Opts{
|
||||
PB: pocketbaseApp,
|
||||
USSDClient: ussdClient,
|
||||
})
|
||||
router.Bootsrap()
|
||||
|
||||
if err := pocketbaseApp.Start(); err != nil {
|
||||
lo.Error("could not start pocketbase ", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
22
dev/docker-compose.yaml
Normal file
22
dev/docker-compose.yaml
Normal file
@ -0,0 +1,22 @@
|
||||
version: "3.9"
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
user: postgres
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
- POSTGRES_USER=postgres
|
||||
volumes:
|
||||
- farmstar-pg:/var/lib/postgresql/data
|
||||
- ./init_db.sql:/docker-entrypoint-initdb.d/init_db.sql
|
||||
ports:
|
||||
- "127.0.0.1:5432:5432"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
volumes:
|
||||
farmstar-pg:
|
||||
driver: local
|
1
dev/init_db.sql
Normal file
1
dev/init_db.sql
Normal file
@ -0,0 +1 @@
|
||||
CREATE DATABASE farmstar_worker;
|
113
go.mod
Normal file
113
go.mod
Normal file
@ -0,0 +1,113 @@
|
||||
module github.com/grassrootseconomics/farmstar-survey-backend
|
||||
|
||||
go 1.21.6
|
||||
|
||||
require (
|
||||
github.com/jackc/pgx/v5 v5.5.1
|
||||
github.com/knadh/koanf/parsers/toml v0.1.0
|
||||
github.com/knadh/koanf/providers/env v0.1.0
|
||||
github.com/knadh/koanf/providers/file v0.1.0
|
||||
github.com/knadh/koanf/v2 v2.0.1
|
||||
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
|
||||
github.com/pocketbase/dbx v1.10.1
|
||||
github.com/pocketbase/pocketbase v0.19.4
|
||||
github.com/riverqueue/river v0.0.16
|
||||
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.0.16
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/aws/aws-sdk-go v1.46.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.21.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.19.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.43 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.92 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.38 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.40.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.15.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.23.2 // indirect
|
||||
github.com/aws/smithy-go v1.15.0 // indirect
|
||||
github.com/carlmjohnson/requests v0.23.5 // indirect
|
||||
github.com/disintegration/imaging v1.6.2 // indirect
|
||||
github.com/domodwyer/mailyak/v3 v3.6.2 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/ganigeorgiev/fexpr v0.3.0 // indirect
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/uuid v1.4.0 // indirect
|
||||
github.com/google/wire v0.5.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/kamikazechaser/africastalking v1.0.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/knadh/koanf/maps v0.1.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/oklog/ulid/v2 v2.1.0 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/riverqueue/river/riverdriver v0.0.15 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/cobra v1.8.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
gocloud.dev v0.34.0 // indirect
|
||||
golang.org/x/crypto v0.18.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||
golang.org/x/image v0.13.0 // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/net v0.20.0 // indirect
|
||||
golang.org/x/oauth2 v0.13.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
golang.org/x/term v0.16.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.16.1 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/api v0.148.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
|
||||
google.golang.org/grpc v1.59.0 // indirect
|
||||
google.golang.org/protobuf v1.32.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
modernc.org/cc/v3 v3.41.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.15 // indirect
|
||||
modernc.org/libc v1.28.0 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.7.2 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/sqlite v1.26.0 // indirect
|
||||
modernc.org/strutil v1.2.0 // indirect
|
||||
modernc.org/token v1.1.0 // indirect
|
||||
)
|
420
go.sum
Normal file
420
go.sum
Normal file
@ -0,0 +1,420 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.110.7 h1:rJyC7nWRg2jWGZ4wSJ5nY65GTdYJkg0cd/uXb+ACI6o=
|
||||
cloud.google.com/go v0.110.7/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI=
|
||||
cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY=
|
||||
cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y=
|
||||
cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU=
|
||||
cloud.google.com/go/storage v1.31.0 h1:+S3LjjEN2zZ+L5hOwj4+1OkGCsLVe0NzpXKQ1pSdTCI=
|
||||
cloud.google.com/go/storage v1.31.0/go.mod h1:81ams1PrhW16L4kF7qg+4mTq7SRs5HsbDTM0bWvrwJ0=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/aws/aws-sdk-go v1.46.6 h1:6wFnNC9hETIZLMf6SOTN7IcclrOGwp/n9SLp8Pjt6E8=
|
||||
github.com/aws/aws-sdk-go v1.46.6/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/aws/aws-sdk-go-v2 v1.21.2 h1:+LXZ0sgo8quN9UOKXXzAWRT3FWd4NxeXWOZom9pE7GA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.21.2/go.mod h1:ErQhvNuEMhJjweavOYhxVkn2RUx7kQXVATHrjKtxIpM=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.14 h1:Sc82v7tDQ/vdU1WtuSyzZ1I7y/68j//HJ6uozND1IDs=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.14/go.mod h1:9NCTOURS8OpxvoAVHq79LK81/zC78hfRWFn+aL0SPcY=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.19.1 h1:oe3vqcGftyk40icfLymhhhNysAwk0NfiwkDi2GTPMXs=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.19.1/go.mod h1:ZwDUgFnQgsazQTnWfeLWk5GjeqTQTL8lMkoE1UXzxdE=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.43 h1:LU8vo40zBlo3R7bAvBVy/ku4nxGEyZe9N8MqAeFTzF8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.43/go.mod h1:zWJBz1Yf1ZtX5NGax9ZdNjhhI4rgjfgsyk6vTY1yfVg=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13 h1:PIktER+hwIG286DqXyvVENjgLTAwGgoeriLDD5C+YlQ=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13/go.mod h1:f/Ib/qYjhV2/qdsf79H3QP/eRE4AkVyEf6sk7XfZ1tg=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.92 h1:nLA7dGFC6v4P6b+hzqt5GqIGmIuN+jTJzojfdOLXWFE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.92/go.mod h1:h+ei9z19AhoN+Dac92DwkzfbJ4mFUea92xgl5pKSG0Q=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43 h1:nFBQlGtkbPzp/NjZLuFxRqmT91rLJkgvsEQs68h962Y=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43/go.mod h1:auo+PiyLl0n1l8A0e8RIeR8tOzYPfZZH/JNlrJ8igTQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37 h1:JRVhO25+r3ar2mKGP7E0LDl8K9/G36gjlqca5iQbaqc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37/go.mod h1:Qe+2KtKml+FEsQF/DHmDV+xjtche/hwoF75EG4UlHW8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45 h1:hze8YsjSh8Wl1rYa1CJpRmXP21BvOBuc76YhW0HsuQ4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45/go.mod h1:lD5M20o09/LCuQ2mE62Mb/iSdSlCNuj6H5ci7tW7OsE=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.6 h1:wmGLw2i8ZTlHLw7a9ULGfQbuccw8uIiNr6sol5bFzc8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.6/go.mod h1:Q0Hq2X/NuL7z8b1Dww8rmOFl+jzusKEcyvkKspwdpyc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.15 h1:7R8uRYyXzdD71KWVCL78lJZltah6VVznXBazvKjfH58=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.15/go.mod h1:26SQUPcTNgV1Tapwdt4a1rOsYRsnBsJHLMPoxK2b0d8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.38 h1:skaFGzv+3kA+v2BPKhuekeb1Hbb105+44r8ASC+q5SE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.38/go.mod h1:epIZoRSSbRIwLPJU5F+OldHhwZPBdpDeQkRdCeY3+00=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37 h1:WWZA/I2K4ptBS1kg0kV1JbBtG/umed0vwHRrmcr9z7k=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37/go.mod h1:vBmDnwWXWxNPFRMmG2m/3MKOe+xEcMDo1tanpaWCcck=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.6 h1:9ulSU5ClouoPIYhDQdg9tpl83d5Yb91PXTKK+17q+ow=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.6/go.mod h1:lnc2taBsR9nTlz9meD+lhFZZ9EWY712QHrRflWpTcOA=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.40.2 h1:Ll5/YVCOzRB+gxPqs2uD0R7/MyATC0w85626glSKmp4=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.40.2/go.mod h1:Zjfqt7KhQK+PO1bbOsFNzKgaq7TcxzmEoDWN8lM0qzQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.15.2 h1:JuPGc7IkOP4AaqcZSIcyqLpFSqBWK32rM9+a1g6u73k=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.15.2/go.mod h1:gsL4keucRCgW+xA85ALBpRFfdSLH4kHOVSnLMSuBECo=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3 h1:HFiiRkf1SdaAmV3/BHOFZ9DjFynPHj8G/UIO1lQS+fk=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3/go.mod h1:a7bHA82fyUXOm+ZSWKU6PIoBxrjSprdLoM8xPYvzYVg=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.23.2 h1:0BkLfgeDjfZnZ+MhB3ONb01u9pwFYTCZVhlsSSBvlbU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.23.2/go.mod h1:Eows6e1uQEsc4ZaHANmsPRzAKcVDrcmjjWiih2+HUUQ=
|
||||
github.com/aws/smithy-go v1.15.0 h1:PS/durmlzvAFpQHDs4wi4sNNP9ExsqZh6IlfdHXgKK8=
|
||||
github.com/aws/smithy-go v1.15.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/carlmjohnson/requests v0.23.5 h1:NPANcAofwwSuC6SIMwlgmHry2V3pLrSqRiSBKYbNHHA=
|
||||
github.com/carlmjohnson/requests v0.23.5/go.mod h1:zG9P28thdRnN61aD7iECFhH5iGGKX2jIjKQD9kqYH+o=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
|
||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8=
|
||||
github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/ganigeorgiev/fexpr v0.3.0 h1:RwSyJBME+g/XdzlUW0paH/4VXhLHPg+rErtLeC7K8Ew=
|
||||
github.com/ganigeorgiev/fexpr v0.3.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
|
||||
github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-replayers/grpcreplay v1.1.0 h1:S5+I3zYyZ+GQz68OfbURDdt/+cSMqCK1wrvNx7WBzTE=
|
||||
github.com/google/go-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6AwLM6miiXUQwP1S+I9icmhk=
|
||||
github.com/google/go-replayers/httpreplay v1.2.0 h1:VM1wEyyjaoU53BwrOnaf9VhAyQQEEioJvFYxYcLRKzk=
|
||||
github.com/google/go-replayers/httpreplay v1.2.0/go.mod h1:WahEFFZZ7a1P4VM1qEeHy+tME4bwyqPcwWbNlUI1Mcg=
|
||||
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
|
||||
github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8 h1:gpptm606MZYGaMHMsB4Srmb6EbW/IVHnt04rcMXnkBQ=
|
||||
github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
|
||||
github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw=
|
||||
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.5.1 h1:5I9etrGkLrN+2XPCsi6XLlV5DITbSL/xBZdmAxFcXPI=
|
||||
github.com/jackc/pgx/v5 v5.5.1/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/kamikazechaser/africastalking v1.0.0 h1:UI5BXgj87o5lJrrlUikY86qCSvt97CJgTETPo/u3osU=
|
||||
github.com/kamikazechaser/africastalking v1.0.0/go.mod h1:hgKqb7Zl080ay0BzCBI25iqnoK2ac1tWYPfquXODGwc=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
|
||||
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
||||
github.com/knadh/koanf/parsers/toml v0.1.0 h1:S2hLqS4TgWZYj4/7mI5m1CQQcWurxUz6ODgOub/6LCI=
|
||||
github.com/knadh/koanf/parsers/toml v0.1.0/go.mod h1:yUprhq6eo3GbyVXFFMdbfZSo928ksS+uo0FFqNMnO18=
|
||||
github.com/knadh/koanf/providers/env v0.1.0 h1:LqKteXqfOWyx5Ab9VfGHmjY9BvRXi+clwyZozgVRiKg=
|
||||
github.com/knadh/koanf/providers/env v0.1.0/go.mod h1:RE8K9GbACJkeEnkl8L/Qcj8p4ZyPXZIQ191HJi44ZaQ=
|
||||
github.com/knadh/koanf/providers/file v0.1.0 h1:fs6U7nrV58d3CFAFh8VTde8TM262ObYf3ODrc//Lp+c=
|
||||
github.com/knadh/koanf/providers/file v0.1.0/go.mod h1:rjJ/nHQl64iYCtAW2QQnF0eSmDEX/YZ/eNFj5yR6BvA=
|
||||
github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g=
|
||||
github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 h1:FwuzbVh87iLiUQj1+uQUsuw9x5t9m5n5g7rG7o4svW4=
|
||||
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61/go.mod h1:paQfF1YtHe+GrGg5fOgjsjoCX/UKDr9bc1DoWpZfns8=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
|
||||
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
||||
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pocketbase/dbx v1.10.1 h1:cw+vsyfCJD8YObOVeqb93YErnlxwYMkNZ4rwN0G0AaA=
|
||||
github.com/pocketbase/dbx v1.10.1/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
|
||||
github.com/pocketbase/pocketbase v0.19.4 h1:PtgbrNMg2wZqI4BJqnvnc/RtDOYan+qcQyJqf2diLp4=
|
||||
github.com/pocketbase/pocketbase v0.19.4/go.mod h1:P6efmT5amltbiSLbdG42D+yPAkKv0Jg449k6HHyAu5w=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/riverqueue/river v0.0.16 h1:rqbWGyiGMLD2OkzzPHhyR1Oh0l80ZPavXLH8+cerTIs=
|
||||
github.com/riverqueue/river v0.0.16/go.mod h1:1zLtldMgoTfDj4ouXFNGsF4bzqb9Y2Ds3WuDFanugJ0=
|
||||
github.com/riverqueue/river/riverdriver v0.0.15 h1:BB26eGIB+xK4dpQ9w5WUxWHbDZbk0E+tmajGRYvI/hM=
|
||||
github.com/riverqueue/river/riverdriver v0.0.15/go.mod h1:vtgL7tRTSB6rzeVEDppehd/rPx3Is+WBYb17Zj0+KsE=
|
||||
github.com/riverqueue/river/riverdriver/riverdatabasesql v0.0.15 h1:vBS22g1I3gaSRnYnk9yrvn+oTk0odVTmJw1pIDAFD5w=
|
||||
github.com/riverqueue/river/riverdriver/riverdatabasesql v0.0.15/go.mod h1:ERxJyW2g+1oBzTn5MRfSWi6Z83I5Dumj9J+E4rCe2kc=
|
||||
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.0.16 h1:kUr40A6sVrw3blSxQQo3T0LgiO6qRbSzK2u4Vjml6Ww=
|
||||
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.0.16/go.mod h1:w365SHh6QB96Yea/SsGBdUAhGGvlWhU9+v2AVwJSjBc=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
gocloud.dev v0.34.0 h1:LzlQY+4l2cMtuNfwT2ht4+fiXwWf/NmPTnXUlLmGif4=
|
||||
gocloud.dev v0.34.0/go.mod h1:psKOachbnvY3DAOPbsFVmLIErwsbWPUG2H5i65D38vE=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg=
|
||||
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY=
|
||||
golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
|
||||
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
google.golang.org/api v0.148.0 h1:HBq4TZlN4/1pNcu0geJZ/Q50vIwIXT532UIMYoo0vOs=
|
||||
google.golang.org/api v0.148.0/go.mod h1:8/TBgwaKjfqTdacOJrOv2+2Q6fBDU1uHKK06oGSkxzU=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a h1:fwgW9j3vHirt4ObdHoYNwuO24BEZjSzbh+zPaNWoiY8=
|
||||
google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:EMfReVxb80Dq1hhioy0sOsY9jCE46YDgHlJ7fWVUWRE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q=
|
||||
modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y=
|
||||
modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0=
|
||||
modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI=
|
||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
modernc.org/libc v1.28.0 h1:kHB6LtDBV8DEAK7aZT1vWvP92abW9fb8cjb1P9UTpUE=
|
||||
modernc.org/libc v1.28.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
||||
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.26.0 h1:SocQdLRSYlA8W99V8YH0NES75thx19d9sB/aFc4R8Lw=
|
||||
modernc.org/sqlite v1.26.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
|
||||
modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
|
||||
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
|
40
internal/hooks/distributor.go
Normal file
40
internal/hooks/distributor.go
Normal file
@ -0,0 +1,40 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/grassrootseconomics/farmstar-survey-backend/internal/worker"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
func (r *HooksContainer) bootstrapDistributorHook() {
|
||||
r.pb.OnModelAfterCreate("distributor_base").Add(func(e *core.ModelEvent) error {
|
||||
farmerRecord, err := r.pb.Dao().FindRecordById("distributor_base", e.Model.GetId())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userRecord, err := r.pb.Dao().FindRecordById("users", farmerRecord.GetString("user"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
phone := userRecord.GetString("phone")
|
||||
|
||||
msg := fmt.Sprintf(
|
||||
"New distributors's survey completed:\n\nname: %s\nphone: %s",
|
||||
userRecord.GetString("name"),
|
||||
userRecord.GetString("phone"),
|
||||
)
|
||||
|
||||
if err := r.worker.QueueSMSTask(phone, worker.DistributorComplete, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.worker.QueueTgTask(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
44
internal/hooks/farmer.go
Normal file
44
internal/hooks/farmer.go
Normal file
@ -0,0 +1,44 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/grassrootseconomics/farmstar-survey-backend/internal/worker"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
func (r *HooksContainer) bootstrapFarmerHook() {
|
||||
r.pb.OnModelAfterCreate("farmer_farm").Add(func(e *core.ModelEvent) error {
|
||||
farmerRecord, err := r.pb.Dao().FindRecordById("farmer_farm", e.Model.GetId())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userRecord, err := r.pb.Dao().FindRecordById("users", farmerRecord.GetString("user"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
phone := userRecord.GetString("phone")
|
||||
|
||||
msg := fmt.Sprintf(
|
||||
"New farmer's survey completed:\n\nname: %s\nphone: %s",
|
||||
userRecord.GetString("name"),
|
||||
phone,
|
||||
)
|
||||
|
||||
if err := r.worker.QueueSMSTask(phone, worker.FarmerComplete, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.worker.QueueTgTask(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.worker.QueueRewardsTask(phone, 130_000_000); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
36
internal/hooks/hooks.go
Normal file
36
internal/hooks/hooks.go
Normal file
@ -0,0 +1,36 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"github.com/grassrootseconomics/farmstar-survey-backend/internal/worker"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
)
|
||||
|
||||
type (
|
||||
Opts struct {
|
||||
PB *pocketbase.PocketBase
|
||||
Worker *worker.Worker
|
||||
RedemptionVault string
|
||||
}
|
||||
|
||||
HooksContainer struct {
|
||||
pb *pocketbase.PocketBase
|
||||
worker *worker.Worker
|
||||
redemptionVault string
|
||||
}
|
||||
)
|
||||
|
||||
func NewHooks(o Opts) *HooksContainer {
|
||||
return &HooksContainer{
|
||||
pb: o.PB,
|
||||
worker: o.Worker,
|
||||
redemptionVault: o.RedemptionVault,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *HooksContainer) Bootsrap() {
|
||||
r.bootstrapRegistrationHook()
|
||||
r.bootstrapFarmerHook()
|
||||
r.bootstrapDistributorHook()
|
||||
r.bootstrapTransactionHook()
|
||||
r.bootstrapRedeemHook()
|
||||
}
|
71
internal/hooks/redeem.go
Normal file
71
internal/hooks/redeem.go
Normal file
@ -0,0 +1,71 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
var (
|
||||
distributors = map[string]string{
|
||||
"maraba_investments": "Maraba Investments",
|
||||
"farmers_center": "Farmers Center",
|
||||
"farmers_world": "Farmers World",
|
||||
"farmers_desk": "Farmers Desk",
|
||||
"mazao_na_afya": "Mazao na Afya",
|
||||
}
|
||||
|
||||
rewardsSchedule = map[int]string{
|
||||
60: "25% discount on a 50kg bag of EverGrow",
|
||||
120: "50% discount on a 50kg bag of EverGrow",
|
||||
180: "75% discount on a 50kg bag of EverGrow",
|
||||
240: "1 free 50kg bag of EverGrow",
|
||||
}
|
||||
)
|
||||
|
||||
func (r *HooksContainer) bootstrapRedeemHook() {
|
||||
r.pb.OnModelAfterCreate("redemption").Add(func(e *core.ModelEvent) error {
|
||||
redemptionRecord, err := r.pb.Dao().FindRecordById("redemption", e.Model.GetId())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
initiatorRecord, err := r.pb.Dao().FindRecordById("users", redemptionRecord.GetString("distributor_initiator"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
counterpartyRecord, err := r.pb.Dao().FindRecordById("users", redemptionRecord.GetString("farmer"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tgMsg := fmt.Sprintf(
|
||||
"New redemption request:\n\ndistributor name: %s\ndistributor entity: %s\nfarmer name: %s\nredemption request: %d FSP",
|
||||
initiatorRecord.GetString("name"),
|
||||
redemptionRecord.GetString("distributor"),
|
||||
counterpartyRecord.GetString("name"),
|
||||
redemptionRecord.GetInt("fsp_redemption_request"),
|
||||
)
|
||||
|
||||
if err := r.worker.QueueTgTask(tgMsg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.worker.QueueSMSTask(
|
||||
counterpartyRecord.GetString("phone"),
|
||||
"",
|
||||
fmt.Sprintf(
|
||||
"FSP redemption request received at %s for %s. Send %d FSP to %s to complete redemption.",
|
||||
distributors[redemptionRecord.GetString("distributor")],
|
||||
rewardsSchedule[redemptionRecord.GetInt("fsp_redemption_request")],
|
||||
redemptionRecord.GetInt("fsp_redemption_request"),
|
||||
r.redemptionVault,
|
||||
),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
43
internal/hooks/registration.go
Normal file
43
internal/hooks/registration.go
Normal file
@ -0,0 +1,43 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/grassrootseconomics/farmstar-survey-backend/internal/worker"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
func (r *HooksContainer) bootstrapRegistrationHook() {
|
||||
r.pb.OnModelAfterCreate("users").Add(func(e *core.ModelEvent) error {
|
||||
userRecord, err := r.pb.Dao().FindRecordById("users", e.Model.GetId())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
participantType := userRecord.GetString("participant_type")
|
||||
participantPhone := userRecord.GetString("phone")
|
||||
|
||||
tgMsg := fmt.Sprintf(
|
||||
"New %s signup:\n\nname: %s\nphone: %s",
|
||||
participantType,
|
||||
userRecord.GetString("name"),
|
||||
participantPhone,
|
||||
)
|
||||
|
||||
if err := r.worker.QueueTgTask(tgMsg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if participantType == "farmer" {
|
||||
if err := r.worker.QueueSMSTask(participantPhone, worker.FarmerRegistration, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := r.worker.QueueSMSTask(participantPhone, worker.DistributorRegistration, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
93
internal/hooks/transaction.go
Normal file
93
internal/hooks/transaction.go
Normal file
@ -0,0 +1,93 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/grassrootseconomics/farmstar-survey-backend/internal/worker"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
func (r *HooksContainer) bootstrapTransactionHook() {
|
||||
r.pb.OnModelAfterCreate("transactions").Add(func(e *core.ModelEvent) error {
|
||||
log.Println("hook?")
|
||||
transactionRecord, err := r.pb.Dao().FindRecordById("transactions", e.Model.GetId())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
quantity := uint(transactionRecord.GetInt("evergrow_quantity"))
|
||||
|
||||
initiatorRecord, err := r.pb.Dao().FindRecordById("users", transactionRecord.GetString("initiator"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
counterpartyRecord, err := r.pb.Dao().FindRecordById("users", transactionRecord.GetString("counterparty"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
inverseTransactionRecord, err := r.pb.Dao().FindFirstRecordByFilter(
|
||||
"transactions",
|
||||
"completed = {:completed} && evergrow_quantity = {:quantity} && initiator = {:initiator} && counterparty = {:counterparty}",
|
||||
dbx.Params{
|
||||
"completed": false,
|
||||
"quantity": transactionRecord.GetInt("evergrow_quantity"),
|
||||
"initiator": counterpartyRecord.GetId(),
|
||||
"counterparty": initiatorRecord.GetId(),
|
||||
},
|
||||
)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return err
|
||||
}
|
||||
|
||||
if inverseTransactionRecord != nil {
|
||||
if isFarmer(initiatorRecord.GetString("participant_type")) {
|
||||
if err := r.worker.QueueRewardsTask(initiatorRecord.GetString("phone"), 65_000_000*quantity); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if isFarmer(counterpartyRecord.GetString("participant_type")) {
|
||||
if err := r.worker.QueueRewardsTask(counterpartyRecord.GetString("phone"), 65_000_000*quantity); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
transactionRecord.Set("completed", true)
|
||||
if err := r.pb.Dao().SaveRecord(transactionRecord); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
inverseTransactionRecord.Set("completed", true)
|
||||
if err := r.pb.Dao().SaveRecord(inverseTransactionRecord); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.worker.QueueSMSTask(initiatorRecord.GetString("phone"), worker.PurchaseComplete, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf(
|
||||
"New purchase survey completed:\n\ninitiator: %s\ncounterparty: %s\nquantity: %d\nfeedback: %s",
|
||||
fmt.Sprintf("%s (%s)", initiatorRecord.GetString("name"), initiatorRecord.GetString("participant_type")),
|
||||
fmt.Sprintf("%s (%s)", counterpartyRecord.GetString("name"), counterpartyRecord.GetString("participant_type")),
|
||||
transactionRecord.GetInt("evergrow_quantity"),
|
||||
transactionRecord.GetString("feedback"),
|
||||
)
|
||||
|
||||
if err := r.worker.QueueTgTask(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func isFarmer(value string) bool {
|
||||
return value == "farmer"
|
||||
}
|
101
internal/router/distributor.go
Normal file
101
internal/router/distributor.go
Normal file
@ -0,0 +1,101 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
"github.com/pocketbase/pocketbase/forms"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
)
|
||||
|
||||
func (r *RouterContainer) bootstrapDistributorRoute() {
|
||||
r.PB.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
e.Router.POST("/distributor", func(c echo.Context) error {
|
||||
requestData := struct {
|
||||
Phone string `json:"phone"`
|
||||
Distributor string `json:"distributor_name"`
|
||||
FertilizerType string `json:"type"`
|
||||
SyntheticPercentage string `json:"synthetic_percentage"`
|
||||
OrganicPercentage string `json:"organic_percentage"`
|
||||
SyntheticFactors []string `json:"synthetic_factors"`
|
||||
OrganicFactors []string `json:"organic_factors"`
|
||||
Trends string `json:"trends"`
|
||||
Obstacles string `json:"obstacles"`
|
||||
NeedEducation string `json:"need_education"`
|
||||
FarmerStrategies string `json:"farmer_strategies"`
|
||||
FarmstarStrategies string `json:"farmstar_strategies"`
|
||||
}{}
|
||||
if err := c.Bind(&requestData); err != nil {
|
||||
return apis.NewBadRequestError("Failed to read request data", err)
|
||||
}
|
||||
|
||||
if err := r.PB.Dao().RunInTransaction(func(txDao *daos.Dao) error {
|
||||
userRecord, err := r.PB.Dao().FindFirstRecordByData("users", "phone", requestData.Phone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if userRecord.GetString("participant_type") != "distributor" {
|
||||
return apis.NewNotFoundError("User is not registered as a distributor", err)
|
||||
}
|
||||
|
||||
distributorExt, err := r.PB.Dao().FindCollectionByNameOrId("distributor_both_fertilizers")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
distributorExtRecord := models.NewRecord(distributorExt)
|
||||
distributorExtForm := forms.NewRecordUpsert(r.PB, distributorExtRecord)
|
||||
distributorExtForm.SetDao(txDao)
|
||||
|
||||
distributorExtForm.LoadData(map[string]any{
|
||||
"user": userRecord.Id,
|
||||
"synthetic_percentage": requestData.SyntheticPercentage,
|
||||
"organic_percentage": requestData.OrganicPercentage,
|
||||
"synthetic_factors": requestData.SyntheticFactors,
|
||||
"organic_factors": requestData.OrganicFactors,
|
||||
"trends": requestData.Trends,
|
||||
"obstacles": requestData.Obstacles,
|
||||
"need_education": requestData.NeedEducation,
|
||||
"farmer_strategies": requestData.FarmerStrategies,
|
||||
})
|
||||
|
||||
if err := distributorExtForm.Submit(); err != nil {
|
||||
return apis.NewBadRequestError("Failed to submit distributor details", err)
|
||||
}
|
||||
|
||||
distributorBaseCollection, err := r.PB.Dao().FindCollectionByNameOrId("distributor_base")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
distributorBaseRecord := models.NewRecord(distributorBaseCollection)
|
||||
distributorBaseForm := forms.NewRecordUpsert(r.PB, distributorBaseRecord)
|
||||
distributorBaseForm.SetDao(txDao)
|
||||
|
||||
distributorBaseForm.LoadData(map[string]any{
|
||||
"user": userRecord.Id,
|
||||
"distributor_name": requestData.Distributor,
|
||||
"fertilizer_type": requestData.FertilizerType,
|
||||
})
|
||||
|
||||
if err := distributorBaseForm.Submit(); err != nil {
|
||||
return apis.NewBadRequestError("Failed to submit additional distributor details", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
c.JSON(400, map[string]any{"ok": "false", "error": err.Error()})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
c.JSON(200, map[string]any{"ok": "true"})
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
213
internal/router/farmer.go
Normal file
213
internal/router/farmer.go
Normal file
@ -0,0 +1,213 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
"github.com/pocketbase/pocketbase/forms"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
)
|
||||
|
||||
func (r *RouterContainer) bootstrapFarmerSurveyRoute() {
|
||||
r.PB.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
e.Router.POST("/farmer", func(c echo.Context) error {
|
||||
requestData := struct {
|
||||
Phone string `json:"phone"`
|
||||
County string `json:"county" `
|
||||
SubCounty string `json:"sub_county"`
|
||||
FarmingAreaAcres string `json:"farming_area_acres"`
|
||||
PlannedCrops []string `json:"planned_crops"`
|
||||
PastHarvestDetails []struct {
|
||||
CropType string `json:"crop_type"`
|
||||
AverageHarvest string `json:"average_harvest"`
|
||||
AverageEarning string `json:"average_earning"`
|
||||
} `json:"past_harvest_details"`
|
||||
TotalExpenditure string `json:"total_expenditure"`
|
||||
SeedsExpenditurePercentage string `json:"seeds_expenditure_percentage"`
|
||||
FertilizerExpenditurePercentage string `json:"fertilizer_expenditure_percentage"`
|
||||
CropsProtectionExpenditurePercentage string `json:"crops_protection_expenditure_percentage"`
|
||||
SyntheticFertilizersExpenditurePercentage string `json:"synthetic_fertilizers_expenditure_percentage"`
|
||||
NaturalFertilizersExpenditure string `json:"natural_fertilizers_expenditure_percentage"`
|
||||
IncreasedExpenses []struct {
|
||||
ExpenseType string `json:"expense_type"`
|
||||
IncreasedExpenseActions []string `json:"increased_expense_actions"`
|
||||
ExpenseActionOverallEffect string `json:"expense_action_overall_effect"`
|
||||
} `json:"increased_expenses"`
|
||||
EvergrowPast string `json:"evergrow_past"`
|
||||
EvergrowFirst string `json:"evergrow_first"`
|
||||
EvergrowApplication string `json:"evergrow_application"`
|
||||
EvergrowCrops []string `json:"evergrow_crops"`
|
||||
EvergrowYield string `json:"evergrow_yield"`
|
||||
OtherFertilizers string `json:"other_fertilizers"`
|
||||
PurchaseChannels []string `json:"purchase_channels"`
|
||||
OtherFertilizersDetails []struct {
|
||||
OtherFertilizerType string `json:"other_fertilizer_type"`
|
||||
OtherFertilizerApplication string `json:"other_fertilizer_application"`
|
||||
OtherFertilizerCrops []string `json:"other_fertilizer_crops"`
|
||||
} `json:"other_fertilizers_details"`
|
||||
}{}
|
||||
if err := c.Bind(&requestData); err != nil {
|
||||
return apis.NewBadRequestError("Failed to read request data", err)
|
||||
}
|
||||
|
||||
if err := r.PB.Dao().RunInTransaction(func(txDao *daos.Dao) error {
|
||||
userRecord, err := r.PB.Dao().FindFirstRecordByData("users", "phone", requestData.Phone)
|
||||
if err != nil {
|
||||
return apis.NewNotFoundError("Phone number not found", err)
|
||||
}
|
||||
|
||||
if userRecord.GetString("participant_type") != "farmer" {
|
||||
return apis.NewNotFoundError("User is not registered as a farmer", err)
|
||||
}
|
||||
|
||||
farmerFarmCollection, err := r.PB.Dao().FindCollectionByNameOrId("farmer_farm")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
farmerFarmRecord := models.NewRecord(farmerFarmCollection)
|
||||
farmerFarmForm := forms.NewRecordUpsert(r.PB, farmerFarmRecord)
|
||||
farmerFarmForm.SetDao(txDao)
|
||||
|
||||
farmerFarmForm.LoadData(map[string]any{
|
||||
"user": userRecord.Id,
|
||||
"county": requestData.County,
|
||||
"sub_county": requestData.SubCounty,
|
||||
"farming_area_acres": requestData.FarmingAreaAcres,
|
||||
"planned_crops": requestData.PlannedCrops,
|
||||
})
|
||||
|
||||
if err := farmerFarmForm.Submit(); err != nil {
|
||||
return apis.NewBadRequestError("Failed to submit farm details", err)
|
||||
}
|
||||
|
||||
farmerPastHarvestCollection, err := r.PB.Dao().FindCollectionByNameOrId("farmer_past_harvest")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, v := range requestData.PastHarvestDetails {
|
||||
farmerPastHarvestRecord := models.NewRecord(farmerPastHarvestCollection)
|
||||
farmerPastHarvestForm := forms.NewRecordUpsert(r.PB, farmerPastHarvestRecord)
|
||||
farmerPastHarvestForm.SetDao(txDao)
|
||||
|
||||
farmerPastHarvestForm.LoadData(map[string]any{
|
||||
"user": userRecord.Id,
|
||||
"crop": v.CropType,
|
||||
"average_harvest": v.AverageHarvest,
|
||||
"average_earning": v.AverageEarning,
|
||||
})
|
||||
|
||||
if err := farmerPastHarvestForm.Submit(); err != nil {
|
||||
return apis.NewBadRequestError("Failed to submit harvest details", err)
|
||||
}
|
||||
}
|
||||
|
||||
farmerExpenditureBaseCollection, err := r.PB.Dao().FindCollectionByNameOrId("farmer_expenditure_base")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
farmerExpenditureBaseRecord := models.NewRecord(farmerExpenditureBaseCollection)
|
||||
farmerExpenditureBaseFarmForm := forms.NewRecordUpsert(r.PB, farmerExpenditureBaseRecord)
|
||||
farmerExpenditureBaseFarmForm.SetDao(txDao)
|
||||
|
||||
farmerExpenditureBaseFarmForm.LoadData(map[string]any{
|
||||
"user": userRecord.Id,
|
||||
"total_expenditure": requestData.TotalExpenditure,
|
||||
"seeds_expenditure_percentage": requestData.SeedsExpenditurePercentage,
|
||||
"fertilizer_expenditure_percentage": requestData.FertilizerExpenditurePercentage,
|
||||
"crops_protection_expenditure_percentage": requestData.CropsProtectionExpenditurePercentage,
|
||||
"synthetic_fertilizers_expenditure_percentage": requestData.SyntheticFertilizersExpenditurePercentage,
|
||||
"natural_fertilizers_expenditure_percentage": requestData.NaturalFertilizersExpenditure,
|
||||
})
|
||||
|
||||
if err := farmerExpenditureBaseFarmForm.Submit(); err != nil {
|
||||
return apis.NewBadRequestError("Failed to submit expenditure base details", err)
|
||||
}
|
||||
|
||||
farmerExpenditureIncreasedCollection, err := r.PB.Dao().FindCollectionByNameOrId("farmer_expenditure_increased")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, v := range requestData.IncreasedExpenses {
|
||||
farmerExpenditureIncreasedRecord := models.NewRecord(farmerExpenditureIncreasedCollection)
|
||||
farmerExpenditureIncreasedForm := forms.NewRecordUpsert(r.PB, farmerExpenditureIncreasedRecord)
|
||||
farmerExpenditureIncreasedForm.SetDao(txDao)
|
||||
|
||||
farmerExpenditureIncreasedForm.LoadData(map[string]any{
|
||||
"user": userRecord.Id,
|
||||
"expense": v.ExpenseType,
|
||||
"actions": v.IncreasedExpenseActions,
|
||||
"overall_effect": v.ExpenseActionOverallEffect,
|
||||
})
|
||||
|
||||
if err := farmerExpenditureIncreasedForm.Submit(); err != nil {
|
||||
return apis.NewBadRequestError("Failed to submit farmer expenditure increased details", err)
|
||||
}
|
||||
}
|
||||
|
||||
farmerEvergrowCollection, err := r.PB.Dao().FindCollectionByNameOrId("farmer_evergrow")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
farmerEvergrowRecord := models.NewRecord(farmerEvergrowCollection)
|
||||
farmerEvergrowForm := forms.NewRecordUpsert(r.PB, farmerEvergrowRecord)
|
||||
farmerEvergrowForm.SetDao(txDao)
|
||||
|
||||
farmerEvergrowForm.LoadData(map[string]any{
|
||||
"user": userRecord.Id,
|
||||
"evergrow_past": requestData.EvergrowPast,
|
||||
"evergrow_first": requestData.EvergrowFirst,
|
||||
"evergrow_application": requestData.EvergrowApplication,
|
||||
"evergrow_crops": requestData.EvergrowCrops,
|
||||
"evergrow_yield": requestData.EvergrowYield,
|
||||
"other_fertilizers": requestData.OtherFertilizers,
|
||||
"purchase_channels": requestData.PurchaseChannels,
|
||||
})
|
||||
|
||||
if err := farmerEvergrowForm.Submit(); err != nil {
|
||||
return apis.NewBadRequestError("Failed to submit evergrow details", err)
|
||||
}
|
||||
|
||||
farmerOtherFertilizersCollection, err := r.PB.Dao().FindCollectionByNameOrId("farmer_other_fertilizers")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, v := range requestData.OtherFertilizersDetails {
|
||||
farmerOtherFertilizersRecord := models.NewRecord(farmerOtherFertilizersCollection)
|
||||
farmerOtherFertilizersForm := forms.NewRecordUpsert(r.PB, farmerOtherFertilizersRecord)
|
||||
farmerOtherFertilizersForm.SetDao(txDao)
|
||||
|
||||
farmerOtherFertilizersForm.LoadData(map[string]any{
|
||||
"user": userRecord.Id,
|
||||
"fertilizer_type": v.OtherFertilizerType,
|
||||
"fertilizer_application": v.OtherFertilizerApplication,
|
||||
"crops": v.OtherFertilizerCrops,
|
||||
})
|
||||
|
||||
if err := farmerOtherFertilizersForm.Submit(); err != nil {
|
||||
return apis.NewBadRequestError("Failed to submit other fertilizers details", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}); err != nil {
|
||||
c.JSON(400, map[string]any{"ok": "false", "error": err.Error()})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
c.JSON(200, map[string]any{"ok": "true"})
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
79
internal/router/redeem.go
Normal file
79
internal/router/redeem.go
Normal file
@ -0,0 +1,79 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
"github.com/pocketbase/pocketbase/forms"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
)
|
||||
|
||||
func (r *RouterContainer) bootstrapRedeemRoute() {
|
||||
r.PB.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
e.Router.POST("/redeem", func(c echo.Context) error {
|
||||
requestData := struct {
|
||||
DistributorPhone string `json:"distributor_phone"`
|
||||
Distributor string `json:"distributor"`
|
||||
FarmerPhone string `json:"farmer_phone"`
|
||||
Redemption string `json:"redemption_options"`
|
||||
}{}
|
||||
if err := c.Bind(&requestData); err != nil {
|
||||
return apis.NewBadRequestError("Failed to read request data", err)
|
||||
}
|
||||
|
||||
if err := r.PB.Dao().RunInTransaction(func(txDao *daos.Dao) error {
|
||||
distributorRecord, err := r.PB.Dao().FindFirstRecordByData("users", "phone", requestData.DistributorPhone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if distributorRecord.GetString("participant_type") != "distributor" {
|
||||
return apis.NewNotFoundError("User is not registered as a distributor", err)
|
||||
}
|
||||
|
||||
farmerRecord, err := r.PB.Dao().FindFirstRecordByData("users", "phone", requestData.FarmerPhone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if farmerRecord.GetString("participant_type") != "farmer" {
|
||||
return apis.NewNotFoundError("User is not registered as a farmer", err)
|
||||
}
|
||||
|
||||
redemptionCollection, err := r.PB.Dao().FindCollectionByNameOrId("redemption")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
redemptionRecord := models.NewRecord(redemptionCollection)
|
||||
redemptionForm := forms.NewRecordUpsert(r.PB, redemptionRecord)
|
||||
redemptionForm.SetDao(txDao)
|
||||
|
||||
redemptionForm.LoadData(map[string]any{
|
||||
"distributor_initiator": distributorRecord.Id,
|
||||
"distributor": requestData.Distributor,
|
||||
"farmer": farmerRecord.Id,
|
||||
"fsp_redemption_request": requestData.Redemption,
|
||||
})
|
||||
|
||||
if err := redemptionForm.Submit(); err != nil {
|
||||
return apis.NewBadRequestError("Failed to submit distributor details", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}); err != nil {
|
||||
c.JSON(400, map[string]any{"ok": "false", "error": err.Error()})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
c.JSON(200, map[string]any{"ok": "true"})
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
49
internal/router/registration.go
Normal file
49
internal/router/registration.go
Normal file
@ -0,0 +1,49 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/forms"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
)
|
||||
|
||||
func (r *RouterContainer) bootstrapRegistrationRoute() {
|
||||
r.PB.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
e.Router.POST("/registration", func(c echo.Context) error {
|
||||
data := apis.RequestInfo(c).Data
|
||||
phone := data["phone"].(string)
|
||||
|
||||
address, err := r.ussd.GetAddress(c.Request().Context(), phone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if address == "" {
|
||||
return apis.NewNotFoundError("Phone # not registered on Sarafu Network", nil)
|
||||
}
|
||||
|
||||
collection, err := r.PB.Dao().FindCollectionByNameOrId("users")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
record := models.NewRecord(collection)
|
||||
form := forms.NewRecordUpsert(r.PB, record)
|
||||
|
||||
if err := form.LoadRequest(c.Request(), ""); err != nil {
|
||||
return apis.NewBadRequestError("Failed to register", err)
|
||||
}
|
||||
|
||||
if err := form.Submit(); err != nil {
|
||||
return apis.NewBadRequestError("Failed to register", err)
|
||||
}
|
||||
|
||||
c.JSON(200, map[string]any{"ok": "true"})
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
33
internal/router/router.go
Normal file
33
internal/router/router.go
Normal file
@ -0,0 +1,33 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/grassrootseconomics/farmstar-survey-backend/pkg/ussd"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
)
|
||||
|
||||
type (
|
||||
Opts struct {
|
||||
PB *pocketbase.PocketBase
|
||||
USSDClient *ussd.USSDClient
|
||||
}
|
||||
|
||||
RouterContainer struct {
|
||||
PB *pocketbase.PocketBase
|
||||
ussd *ussd.USSDClient
|
||||
}
|
||||
)
|
||||
|
||||
func NewRouter(o Opts) *RouterContainer {
|
||||
return &RouterContainer{
|
||||
PB: o.PB,
|
||||
ussd: o.USSDClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RouterContainer) Bootsrap() {
|
||||
r.bootstrapRegistrationRoute()
|
||||
r.bootstrapFarmerSurveyRoute()
|
||||
r.bootstrapTransactionRoute()
|
||||
r.bootstrapDistributorRoute()
|
||||
r.bootstrapRedeemRoute()
|
||||
}
|
100
internal/router/transaction.go
Normal file
100
internal/router/transaction.go
Normal file
@ -0,0 +1,100 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/forms"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
)
|
||||
|
||||
func (r *RouterContainer) bootstrapTransactionRoute() {
|
||||
r.PB.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
e.Router.POST("/transaction", func(c echo.Context) error {
|
||||
requestData := struct {
|
||||
Phone string `json:"phone"`
|
||||
Tx string `json:"tx" `
|
||||
TxBuyType string `json:"buy"`
|
||||
TxSellType string `json:"sell"`
|
||||
TxBuyDate string `json:"buy_date"`
|
||||
TxSellDate string `json:"sell_date"`
|
||||
Feedback string `json:"feedback"`
|
||||
EvergrowQuantityPurchase string `json:"evergrow_quantity_purchase"`
|
||||
EvergrowQuantitySale string `json:"evergrow_quantity_sale"`
|
||||
DistributorName string `json:"distributor_name"`
|
||||
}{}
|
||||
if err := c.Bind(&requestData); err != nil {
|
||||
return apis.NewBadRequestError("Failed to read request data", err)
|
||||
}
|
||||
|
||||
initiatorRecord, err := r.PB.Dao().FindFirstRecordByData("users", "phone", requestData.Phone)
|
||||
if err != nil {
|
||||
return apis.NewNotFoundError("Initiator not registered", err)
|
||||
}
|
||||
|
||||
txCollection, err := r.PB.Dao().FindCollectionByNameOrId("transactions")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txRecord := models.NewRecord(txCollection)
|
||||
txForm := forms.NewRecordUpsert(r.PB, txRecord)
|
||||
|
||||
txData := map[string]any{
|
||||
"initiator": initiatorRecord.Id,
|
||||
"tx_type": requestData.Tx,
|
||||
"feedback": requestData.Feedback,
|
||||
"completed": false,
|
||||
}
|
||||
|
||||
if requestData.Tx == "buy" {
|
||||
log.Println("buy")
|
||||
counterpartyRecord, err := r.PB.Dao().FindFirstRecordByData("users", "phone", requestData.TxBuyType)
|
||||
if err != nil {
|
||||
return apis.NewNotFoundError("Counterparty not registered", err)
|
||||
}
|
||||
|
||||
if counterpartyRecord.GetString("participant_type") != "distributor" {
|
||||
return apis.NewNotFoundError("Counterparty is not registered as a distributor", err)
|
||||
}
|
||||
|
||||
txData["evergrow_quantity"] = requestData.EvergrowQuantityPurchase
|
||||
txData["distributor_name"] = requestData.DistributorName
|
||||
txData["counterparty"] = counterpartyRecord.Id
|
||||
txData["tx_date"] = requestData.TxBuyDate
|
||||
}
|
||||
|
||||
if requestData.Tx == "sell" {
|
||||
log.Println("sell")
|
||||
counterpartyRecord, err := r.PB.Dao().FindFirstRecordByData("users", "phone", requestData.TxSellType)
|
||||
if err != nil {
|
||||
return apis.NewNotFoundError("", err)
|
||||
}
|
||||
|
||||
if counterpartyRecord.GetString("participant_type") != "farmer" {
|
||||
return apis.NewNotFoundError("Counterparty is not registered as a farmer", err)
|
||||
}
|
||||
|
||||
txData["evergrow_quantity"] = requestData.EvergrowQuantitySale
|
||||
txData["counterparty"] = counterpartyRecord.Id
|
||||
txData["tx_date"] = requestData.TxSellDate
|
||||
}
|
||||
|
||||
txForm.LoadData(txData)
|
||||
|
||||
log.Println("pre_submit")
|
||||
if err := txForm.Submit(); err != nil {
|
||||
log.Println(err)
|
||||
return apis.NewBadRequestError("Failed to submit tx details", err)
|
||||
}
|
||||
|
||||
c.JSON(200, map[string]any{"ok": "true"})
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
79
internal/worker/rewards.go
Normal file
79
internal/worker/rewards.go
Normal file
@ -0,0 +1,79 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/grassrootseconomics/farmstar-survey-backend/pkg/custodial"
|
||||
"github.com/grassrootseconomics/farmstar-survey-backend/pkg/ussd"
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/forms"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/riverqueue/river"
|
||||
)
|
||||
|
||||
type (
|
||||
RewardsArgs struct {
|
||||
Phone string `json:"phone"`
|
||||
Amount uint `json:"amount"`
|
||||
}
|
||||
|
||||
RewardsWorker struct {
|
||||
river.WorkerDefaults[RewardsArgs]
|
||||
|
||||
Pocketbase *pocketbase.PocketBase
|
||||
USSDClient *ussd.USSDClient
|
||||
CustodialClient *custodial.CustodialClient
|
||||
VaultAddress string
|
||||
VoucherAddress string
|
||||
}
|
||||
)
|
||||
|
||||
func (RewardsArgs) Kind() string {
|
||||
return "rewards"
|
||||
}
|
||||
|
||||
func (w *RewardsWorker) Work(ctx context.Context, job *river.Job[RewardsArgs]) error {
|
||||
userRecord, err := w.Pocketbase.Dao().FindFirstRecordByData("users", "phone", job.Args.Phone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rewardsCollection, err := w.Pocketbase.Dao().FindCollectionByNameOrId("rewards")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rewardsRecord := models.NewRecord(rewardsCollection)
|
||||
rewardsForm := forms.NewRecordUpsert(w.Pocketbase, rewardsRecord)
|
||||
|
||||
address, err := w.USSDClient.GetAddress(ctx, job.Args.Phone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if address == "" {
|
||||
return errors.New("ussd account not found")
|
||||
}
|
||||
|
||||
trackingId, err := w.CustodialClient.SignTransfer(ctx, custodial.SignTransferPayload{
|
||||
Amount: job.Args.Amount,
|
||||
To: address,
|
||||
From: w.VaultAddress,
|
||||
VoucherAddress: w.VoucherAddress,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rewardsForm.LoadData(map[string]any{
|
||||
"receiver": userRecord.Id,
|
||||
"ge_tracking_id": trackingId,
|
||||
"value": job.Args.Amount / 1_000_000,
|
||||
})
|
||||
|
||||
if err := rewardsForm.Submit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
56
internal/worker/sms.go
Normal file
56
internal/worker/sms.go
Normal file
@ -0,0 +1,56 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/kamikazechaser/africastalking"
|
||||
"github.com/riverqueue/river"
|
||||
)
|
||||
|
||||
type (
|
||||
SMSType string
|
||||
SMSArgs struct {
|
||||
SMS SMSType `json:"SMS"`
|
||||
Phone string `json:"phone"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
SMSWorker struct {
|
||||
river.WorkerDefaults[SMSArgs]
|
||||
|
||||
AtClient *africastalking.AtClient
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
FarmerRegistration SMSType = "You have registered as a farmer for the FarmStar rewards program. Follow the link https://t.ly/Vin2T to complete your registration and receive FSP."
|
||||
DistributorRegistration SMSType = "You have registered as a distributor for the FarmStar rewards program. Follow the link https://t.ly/U7SbY to complete your registration and receive FSP."
|
||||
FarmerComplete SMSType = "Thank you for completing the farmer survey, rewards will be sent soon. Use the link https://t.ly/2CxGt to record purchases of EverGrow Organic Fertilizer."
|
||||
DistributorComplete SMSType = "Thank you for completing the distributor survey. Use the link https://t.ly/2CxGt to record purchases of EverGrow Organic Fertilizer."
|
||||
PurchaseComplete SMSType = "Thank you for completing the purchase survey."
|
||||
)
|
||||
|
||||
func (SMSArgs) Kind() string {
|
||||
return "sms"
|
||||
}
|
||||
|
||||
func (w *SMSWorker) Work(ctx context.Context, job *river.Job[SMSArgs]) error {
|
||||
var text string
|
||||
|
||||
if job.Args.Text != "" {
|
||||
text = job.Args.Text
|
||||
} else {
|
||||
text = string(job.Args.SMS)
|
||||
}
|
||||
|
||||
_, err := w.AtClient.SendBulkSMS(ctx, africastalking.BulkSMSInput{
|
||||
To: []string{"+254" + job.Args.Phone[1:]},
|
||||
From: "Sarafu",
|
||||
Message: text,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
39
internal/worker/telegram.go
Normal file
39
internal/worker/telegram.go
Normal file
@ -0,0 +1,39 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grassrootseconomics/farmstar-survey-backend/pkg/telegram"
|
||||
"github.com/riverqueue/river"
|
||||
)
|
||||
|
||||
type TgMessage string
|
||||
|
||||
type (
|
||||
TelegramArgs struct {
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
TelegramWorker struct {
|
||||
river.WorkerDefaults[TelegramArgs]
|
||||
|
||||
TgClient *telegram.TelegramClient
|
||||
ChatID string
|
||||
}
|
||||
)
|
||||
|
||||
func (TelegramArgs) Kind() string {
|
||||
return "telegram"
|
||||
}
|
||||
|
||||
func (w *TelegramWorker) Work(ctx context.Context, job *river.Job[TelegramArgs]) error {
|
||||
_, err := w.TgClient.SendMessage(ctx, telegram.MessageInput{
|
||||
ChatID: w.ChatID,
|
||||
Text: job.Args.Msg,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
68
internal/worker/worker.go
Normal file
68
internal/worker/worker.go
Normal file
@ -0,0 +1,68 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/riverqueue/river"
|
||||
)
|
||||
|
||||
type (
|
||||
Worker struct {
|
||||
Client *river.Client[pgx.Tx]
|
||||
}
|
||||
)
|
||||
|
||||
func NewWorker(riverClient *river.Client[pgx.Tx]) *Worker {
|
||||
return &Worker{
|
||||
Client: riverClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Worker) Start() error {
|
||||
return w.Client.Start(context.Background())
|
||||
}
|
||||
|
||||
func (w *Worker) Stop() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
return w.Client.Stop(ctx)
|
||||
}
|
||||
|
||||
func (w *Worker) QueueRewardsTask(phone string, amount uint) error {
|
||||
_, err := w.Client.Insert(context.Background(), RewardsArgs{
|
||||
Phone: phone,
|
||||
Amount: amount,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Worker) QueueSMSTask(phone string, SMS SMSType, text string) error {
|
||||
_, err := w.Client.Insert(context.Background(), SMSArgs{
|
||||
Phone: phone,
|
||||
SMS: SMS,
|
||||
Text: text,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Worker) QueueTgTask(msg string) error {
|
||||
_, err := w.Client.Insert(context.Background(), TelegramArgs{
|
||||
Msg: msg,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
1319
migrations/1707137988_collections_snapshot.go
Normal file
1319
migrations/1707137988_collections_snapshot.go
Normal file
File diff suppressed because it is too large
Load Diff
47
pkg/custodial/custodial.go
Normal file
47
pkg/custodial/custodial.go
Normal file
@ -0,0 +1,47 @@
|
||||
package custodial
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/carlmjohnson/requests"
|
||||
)
|
||||
|
||||
type (
|
||||
CustodialClient struct {
|
||||
endpoint string
|
||||
}
|
||||
|
||||
SignTransferPayload struct {
|
||||
Amount uint `json:"amount"`
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
VoucherAddress string `json:"voucherAddress"`
|
||||
}
|
||||
|
||||
CustodialResponse struct {
|
||||
Ok bool `json:"ok"`
|
||||
Result struct {
|
||||
TrackingId string `json:"trackingId"`
|
||||
} `json:"result"`
|
||||
}
|
||||
)
|
||||
|
||||
func New(endpoint string) *CustodialClient {
|
||||
return &CustodialClient{
|
||||
endpoint: endpoint,
|
||||
}
|
||||
}
|
||||
|
||||
func (cc *CustodialClient) SignTransfer(ctx context.Context, payload SignTransferPayload) (string, error) {
|
||||
var resp CustodialResponse
|
||||
|
||||
if err := requests.
|
||||
URL(cc.endpoint).
|
||||
BodyJSON(&payload).
|
||||
ToJSON(&resp).
|
||||
Fetch(ctx); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return resp.Result.TrackingId, nil
|
||||
}
|
52
pkg/telegram/telegram.go
Normal file
52
pkg/telegram/telegram.go
Normal file
@ -0,0 +1,52 @@
|
||||
package telegram
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/carlmjohnson/requests"
|
||||
)
|
||||
|
||||
const (
|
||||
endpoint = "https://api.telegram.org"
|
||||
)
|
||||
|
||||
type (
|
||||
TelegramClient struct {
|
||||
endpoint string
|
||||
chatID string
|
||||
}
|
||||
|
||||
MessageInput struct {
|
||||
ChatID string `json:"chat_id"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
TgResponse struct {
|
||||
Ok bool `json:"ok"`
|
||||
}
|
||||
)
|
||||
|
||||
func New(apiKey string) *TelegramClient {
|
||||
return &TelegramClient{
|
||||
endpoint: endpoint + "/bot" + apiKey + "/",
|
||||
}
|
||||
}
|
||||
|
||||
func (tg *TelegramClient) SendMessage(ctx context.Context, payload MessageInput) (bool, error) {
|
||||
var resp TgResponse
|
||||
|
||||
if err := requests.
|
||||
URL(tg.endpoint + "sendMessage").
|
||||
BodyJSON(&payload).
|
||||
ToJSON(&resp).
|
||||
Fetch(ctx); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !resp.Ok {
|
||||
return false, errors.New("telegram error")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
36
pkg/ussd/ussd.go
Normal file
36
pkg/ussd/ussd.go
Normal file
@ -0,0 +1,36 @@
|
||||
package ussd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/carlmjohnson/requests"
|
||||
)
|
||||
|
||||
type (
|
||||
USSDClient struct {
|
||||
endpoint string
|
||||
}
|
||||
|
||||
USSDResponse struct {
|
||||
Address string `json:"address"`
|
||||
}
|
||||
)
|
||||
|
||||
func New(endpoint string) *USSDClient {
|
||||
return &USSDClient{
|
||||
endpoint: endpoint,
|
||||
}
|
||||
}
|
||||
|
||||
func (uc *USSDClient) GetAddress(ctx context.Context, phone string) (string, error) {
|
||||
var resp USSDResponse
|
||||
|
||||
if err := requests.
|
||||
URL(uc.endpoint + phone).
|
||||
ToJSON(&resp).
|
||||
Fetch(ctx); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return resp.Address, nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user