diff --git a/.gitignore b/.gitignore index 03bd412..d965490 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.env +data/ diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..78f67f6 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +bvplan/templates diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..00b5d86 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + + Preamble + +The GNU General Public License is a free, copyleft license for +software and other kinds of works. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is 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. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + +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. + +To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + +Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + +Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + +Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + +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 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. Use with the GNU Affero General Public License. + +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 Affero 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 special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + +14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of +the GNU 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 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 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 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. + + + Copyright (C) 2023 Dominic Grimm + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) 2023 Dominic Grimm + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + +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 GPL, see +. + +The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Makefile b/Makefile index c7c6c67..597c21a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,9 @@ -.PHONY: all build +.PHONY: all build ci all: build build: + docker compose build + +ci: BUILDKIT_PROGRESS=plain docker compose build diff --git a/backend/src/bin/api.rs b/backend/src/bin/api.rs deleted file mode 100644 index ae61b2f..0000000 --- a/backend/src/bin/api.rs +++ /dev/null @@ -1,31 +0,0 @@ -#[cfg(not(target_env = "msvc"))] -use tikv_jemallocator::Jemalloc; - -#[cfg(not(target_env = "msvc"))] -#[global_allocator] -static GLOBAL: Jemalloc = Jemalloc; - -use actix_cors::Cors; -use actix_web::{http::header, middleware, App, HttpServer}; - -#[actix_web::main] -async fn main() -> std::io::Result<()> { - std::env::set_var("RUST_LOG", "info"); - env_logger::init(); - - let server = HttpServer::new(move || { - App::new() - .wrap( - Cors::default() - .allow_any_origin() - .allowed_methods(vec!["POST", "GET"]) - .allowed_header(header::ACCEPT) - .allowed_header(header::CONTENT_TYPE) - .supports_credentials() - .max_age(3600), - ) - .wrap(middleware::Compress::default()) - .wrap(middleware::Logger::default()) - }); - server.bind("0.0.0.0:80").unwrap().run().await -} diff --git a/backend/src/cache.rs b/backend/src/cache.rs deleted file mode 100644 index b6b1282..0000000 --- a/backend/src/cache.rs +++ /dev/null @@ -1,26 +0,0 @@ -use anyhow::Result; -use lazy_static::lazy_static; -use r2d2_redis::{r2d2, RedisConnectionManager}; - -use crate::config; - -pub type CachePool = r2d2::Pool; -pub type ConnectionPool = r2d2::PooledConnection; - -pub fn establish_connection() -> Result { - Ok(RedisConnectionManager::new( - config::CONFIG.redis_url.as_str(), - )?) -} - -pub fn pool() -> Result { - Ok(r2d2::Pool::builder().build(establish_connection()?)?) -} - -lazy_static! { - pub static ref POOL: CachePool = pool().unwrap(); -} - -pub mod keys { - // pub const LAST_SUBSTITUTION_QUERY_ID: &str = "last_subst_query_id"; -} diff --git a/backend/.dockerignore b/bvplan/.dockerignore similarity index 100% rename from backend/.dockerignore rename to bvplan/.dockerignore diff --git a/backend/.editorconfig b/bvplan/.editorconfig similarity index 100% rename from backend/.editorconfig rename to bvplan/.editorconfig diff --git a/backend/.gitignore b/bvplan/.gitignore similarity index 100% rename from backend/.gitignore rename to bvplan/.gitignore diff --git a/backend/Cargo.lock b/bvplan/Cargo.lock similarity index 86% rename from backend/Cargo.lock rename to bvplan/Cargo.lock index f00e101..f565d9b 100644 --- a/backend/Cargo.lock +++ b/bvplan/Cargo.lock @@ -19,21 +19,6 @@ dependencies = [ "tokio-util", ] -[[package]] -name = "actix-cors" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b340e9cfa5b08690aae90fb61beb44e9b06f44fe3d0f93781aaa58cfba86245e" -dependencies = [ - "actix-utils", - "actix-web", - "derive_more", - "futures-util", - "log", - "once_cell", - "smallvec", -] - [[package]] name = "actix-http" version = "3.3.0" @@ -45,7 +30,7 @@ dependencies = [ "actix-service", "actix-utils", "ahash", - "base64 0.21.0", + "base64", "bitflags", "brotli", "bytes", @@ -147,9 +132,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.3.0" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464e0fddc668ede5f26ec1f9557a8d44eda948732f40c6b0ad79126930eb775f" +checksum = "cd3cb42f9566ab176e1ef0b8b3a896529062b4efc6be0123046095914c4c1c96" dependencies = [ "actix-codec", "actix-http", @@ -182,15 +167,15 @@ dependencies = [ "serde_urlencoded", "smallvec", "socket2", - "time 0.3.17", + "time 0.3.20", "url", ] [[package]] name = "actix-web-codegen" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa9362663c8643d67b2d5eafba49e4cb2c8a053a29ed00a0bea121f17c76b13" +checksum = "2262160a7ae29e3415554a3f1fc04c764b1540c116aa524683208078b7a75bc9" dependencies = [ "actix-router", "proc-macro2", @@ -307,9 +292,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" dependencies = [ "backtrace", ] @@ -320,6 +305,55 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +[[package]] +name = "askama" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb98f10f371286b177db5eeb9a6e5396609555686a35e1d4f7b9a9c6d8af0139" +dependencies = [ + "askama_derive", + "askama_escape", + "askama_shared", +] + +[[package]] +name = "askama_derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87bf87e6e8b47264efa9bde63d6225c6276a52e05e91bf37eaa8afd0032d6b71" +dependencies = [ + "askama_shared", + "proc-macro2", + "syn", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "askama_shared" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf722b94118a07fcbc6640190f247334027685d4e218b794dbfe17c32bf38ed0" +dependencies = [ + "askama_escape", + "humansize", + "mime", + "mime_guess", + "nom", + "num-traits", + "percent-encoding", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", + "toml", +] + [[package]] name = "async-channel" version = "1.8.0" @@ -388,7 +422,7 @@ dependencies = [ "slab", "socket2", "waker-fn", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -421,9 +455,9 @@ checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" [[package]] name = "async-trait" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff18d764974428cf3a9328e23fc5c986f5fbed46e6cd4cdf42544df5d297ec1" +checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" dependencies = [ "proc-macro2", "quote", @@ -453,32 +487,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "backend" -version = "0.1.0" -dependencies = [ - "actix-cors", - "actix-web", - "anyhow", - "celery", - "chrono", - "diesel", - "env_logger", - "envconfig", - "lazy_static", - "log", - "r2d2_redis", - "regex", - "reqwest", - "scraper", - "serde", - "stdext", - "tikv-jemallocator", - "tokio", - "untis", - "url", -] - [[package]] name = "backtrace" version = "0.3.67" @@ -494,18 +502,27 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "base64" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -567,9 +584,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45ea9b00a7b3f2988e9a65ad3917e62123c38dba709b666506207be96d1790b" +checksum = "5ffdb39cb703212f3c11973452c2861b972f757b021158f3516ba10f2fa8b2c1" dependencies = [ "memchr", "serde", @@ -581,6 +598,35 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +[[package]] +name = "bvplan" +version = "0.1.0" +dependencies = [ + "actix-web", + "anyhow", + "askama", + "celery", + "chrono", + "diesel", + "env_logger", + "envconfig", + "fancy-regex", + "flate2", + "lazy_static", + "log", + "minify-html", + "r2d2_redis", + "reqwest", + "scraper", + "serde", + "serde_json", + "stdext", + "tikv-jemallocator", + "tokio", + "untis", + "url", +] + [[package]] name = "byteorder" version = "1.4.3" @@ -589,9 +635,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "bytestring" @@ -613,31 +659,32 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" dependencies = [ "jobserver", ] [[package]] name = "celery" -version = "0.4.0-rcn.11" -source = "git+https://github.com/rusty-celery/rusty-celery.git?branch=main#a966d04188abfb0ea5af417c58c35ffa5af15978" +version = "0.5.2" +source = "git+https://github.com/rusty-celery/rusty-celery.git?branch=main#6a71f338dc6decdb647ebfa48628444e1cfc6582" dependencies = [ "async-trait", - "base64 0.13.1", + "base64", "celery-codegen", "chrono", "colored", "futures", + "futures-lite", "globset", "hostname", "lapin", "log", "once_cell", "rand 0.8.5", - "redis 0.21.7", + "redis 0.22.3", "serde", "serde_json", "thiserror", @@ -650,8 +697,8 @@ dependencies = [ [[package]] name = "celery-codegen" -version = "0.4.0-rcn.11" -source = "git+https://github.com/rusty-celery/rusty-celery.git?branch=main#a966d04188abfb0ea5af417c58c35ffa5af15978" +version = "0.5.2" +source = "git+https://github.com/rusty-celery/rusty-celery.git?branch=main#6a71f338dc6decdb647ebfa48628444e1cfc6582" dependencies = [ "proc-macro2", "quote", @@ -747,7 +794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time 0.3.17", + "time 0.3.20", "version_check", ] @@ -810,6 +857,17 @@ dependencies = [ "typenum", ] +[[package]] +name = "css-minify" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874c6e2d19f8d4a285083b11a3241bfbe01ac3ed85f26e1e6b34888d960552bd" +dependencies = [ + "derive_more", + "indexmap", + "nom", +] + [[package]] name = "cssparser" version = "0.27.2" @@ -838,10 +896,31 @@ dependencies = [ ] [[package]] -name = "cxx" -version = "1.0.88" +name = "csv" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322296e2f2e5af4270b54df9e85a02ff037e271af20ba3e7fe1575515dc840b8" +checksum = "af91f40b7355f82b0a891f50e70399475945bb0b0da4f1700ce60761c9d3e359" +dependencies = [ + "csv-core", + "itoa 1.0.5", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + +[[package]] +name = "cxx" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62" dependencies = [ "cc", "cxxbridge-flags", @@ -851,9 +930,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.88" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "017a1385b05d631e7875b1f151c9f012d37b53491e2a87f65bff5c262b2111d8" +checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690" dependencies = [ "cc", "codespan-reporting", @@ -866,15 +945,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.88" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c26bbb078acf09bc1ecda02d4223f03bdd28bd4874edcb0379138efc499ce971" +checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf" [[package]] name = "cxxbridge-macro" -version = "1.0.88" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357f40d1f06a24b60ae1fe122542c1fb05d28d32acb2aed064e84bc2ad1e252e" +checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892" dependencies = [ "proc-macro2", "quote", @@ -970,9 +1049,9 @@ checksum = "3a68a4904193147e0a8dec3314640e6db742afd5f6e634f428a6af230d9b3591" [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ "cfg-if", ] @@ -1010,6 +1089,27 @@ dependencies = [ "syn", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -1026,10 +1126,20 @@ dependencies = [ ] [[package]] -name = "fastrand" -version = "1.8.0" +name = "fancy-regex" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +dependencies = [ + "bit-set", + "regex", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] @@ -1053,7 +1163,7 @@ dependencies = [ "futures-core", "futures-sink", "pin-project", - "spin 0.9.4", + "spin 0.9.5", ] [[package]] @@ -1086,12 +1196,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs_extra" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" - [[package]] name = "futf" version = "0.1.5" @@ -1104,9 +1208,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" dependencies = [ "futures-channel", "futures-core", @@ -1119,9 +1223,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" dependencies = [ "futures-core", "futures-sink", @@ -1129,15 +1233,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" dependencies = [ "futures-core", "futures-task", @@ -1146,9 +1250,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" [[package]] name = "futures-lite" @@ -1167,9 +1271,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2", "quote", @@ -1178,21 +1282,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ "futures-channel", "futures-core", @@ -1258,9 +1362,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "221996f774192f0f718773def8201c4ae31f02616a54ccfc2d358bb0e5cefdec" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" [[package]] name = "globset" @@ -1354,9 +1458,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", @@ -1386,6 +1490,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "humansize" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" + [[package]] name = "humantime" version = "2.1.0" @@ -1394,9 +1504,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.23" +version = "0.14.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" dependencies = [ "bytes", "futures-channel", @@ -1492,6 +1602,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-lifetimes" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" +dependencies = [ + "libc", + "windows-sys 0.45.0", +] + [[package]] name = "ipnet" version = "2.7.1" @@ -1521,9 +1641,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] @@ -1577,6 +1697,12 @@ dependencies = [ "cc", ] +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "local-channel" version = "0.1.3" @@ -1658,6 +1784,40 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minify-html" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7754d4669873379ea6a8a5b56e406eb83de713af8a791517ef35a0c832b1e7d5" +dependencies = [ + "aho-corasick", + "css-minify", + "lazy_static", + "memchr", + "minify-js", + "rustc-hash", +] + +[[package]] +name = "minify-js" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c300f90ba1138b5c5daf5d9441dc9bdc67b808aac22cf638362a2647bc213be4" +dependencies = [ + "lazy_static", + "parse-js", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1675,14 +1835,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -1765,9 +1925,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "openssl" @@ -1849,15 +2009,26 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.45.0", +] + +[[package]] +name = "parse-js" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30534759e6ad87aa144c396544747e1c25b1020bd133356fd758c8facec764e5" +dependencies = [ + "aho-corasick", + "lazy_static", + "memchr", ] [[package]] @@ -2025,7 +2196,7 @@ dependencies = [ "libc", "log", "wepoll-ffi", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -2081,9 +2252,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ "unicode-ident", ] @@ -2236,9 +2407,9 @@ dependencies = [ [[package]] name = "redis" -version = "0.21.7" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "152f3863635cbb76b73bc247845781098302c6c9ad2060e1a9a7de56840346b6" +checksum = "aa8455fa3621f6b41c514946de66ea0531f57ca017b2e6c7cc368035ea5b46df" dependencies = [ "arc-swap", "async-trait", @@ -2250,7 +2421,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "ryu", - "sha1 0.6.1", + "sha1_smol", "tokio", "tokio-util", "url", @@ -2282,22 +2453,13 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - [[package]] name = "reqwest" version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" dependencies = [ - "base64 0.21.0", + "base64", "bytes", "encoding_rs", "futures-core", @@ -2349,6 +2511,12 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -2358,6 +2526,20 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.36.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + [[package]] name = "rustls" version = "0.20.8" @@ -2400,7 +2582,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64 0.21.0", + "base64", ] [[package]] @@ -2415,7 +2597,7 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -2467,9 +2649,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.8.1" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c4437699b6d34972de58652c68b98cb5b53a4199ab126db8e20ec8ded29a721" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ "bitflags", "core-foundation", @@ -2536,9 +2718,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.91" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" dependencies = [ "itoa 1.0.5", "ryu", @@ -2595,9 +2777,9 @@ checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] @@ -2610,9 +2792,9 @@ checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] @@ -2641,9 +2823,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" +checksum = "7dccf47db1b41fa1573ed27ccf5e08e3ca771cb994f776668c5ebda893b248fc" dependencies = [ "lock_api", ] @@ -2694,9 +2876,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -2717,16 +2899,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" dependencies = [ "cfg-if", "fastrand", - "libc", "redox_syscall", - "remove_dir_all", - "winapi", + "rustix", + "windows-sys 0.42.0", ] [[package]] @@ -2777,12 +2958,11 @@ dependencies = [ [[package]] name = "tikv-jemalloc-sys" -version = "0.5.2+5.3.0-patched" +version = "0.5.3+5.3.0-patched" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec45c14da997d0925c7835883e4d5c181f196fa142f8c19d7643d1e9af2592c3" +checksum = "a678df20055b43e57ef8cddde41cdfda9a3c1a060b67f4c5836dfb1d78543ba8" dependencies = [ "cc", - "fs_extra", "libc", ] @@ -2809,9 +2989,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.17" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ "itoa 1.0.5", "serde", @@ -2827,9 +3007,9 @@ checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "time-macros" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" dependencies = [ "time-core", ] @@ -2845,15 +3025,15 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.24.2" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" dependencies = [ "autocfg", "bytes", @@ -2866,7 +3046,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -2893,9 +3073,9 @@ dependencies = [ [[package]] name = "tokio-native-tls" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", @@ -2917,9 +3097,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" dependencies = [ "futures-core", "pin-project-lite", @@ -2928,9 +3108,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" dependencies = [ "bytes", "futures-core", @@ -2940,6 +3120,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "tower-service" version = "0.3.2" @@ -2979,6 +3168,15 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.10" @@ -3008,12 +3206,13 @@ checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "untis" -version = "0.1.0" -source = "git+https://git.dergrimm.net/dergrimm/untis.rs.git?branch=main#0651a3929683ba9aace219b710951d555253ec26" +version = "0.1.1" +source = "git+https://git.dergrimm.net/dergrimm/untis.rs.git?branch=main#fdb385c5c67bbdbdd60d9e2e75593d29a033f9a2" dependencies = [ "anyhow", "chrono", "cookie", + "csv", "reqwest", "serde", "serde_json", @@ -3045,9 +3244,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "uuid" -version = "0.8.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" dependencies = [ "getrandom 0.2.8", ] @@ -3100,9 +3299,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3110,9 +3309,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", "log", @@ -3125,9 +3324,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" dependencies = [ "cfg-if", "js-sys", @@ -3137,9 +3336,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3147,9 +3346,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", @@ -3160,15 +3359,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" dependencies = [ "js-sys", "wasm-bindgen", @@ -3239,6 +3438,30 @@ dependencies = [ "windows_x86_64_msvc", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.1" @@ -3298,18 +3521,18 @@ checksum = "aed2e7a52e3744ab4d0c05c20aa065258e84c49fd4226f5191b2ed29712710b4" [[package]] name = "zstd" -version = "0.12.2+zstd.1.5.2" +version = "0.12.3+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9262a83dc741c0b0ffec209881b45dbc232c21b02a2b9cb1adb93266e41303d" +checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "6.0.2+zstd.1.5.2" +version = "6.0.4+zstd.1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cf39f730b440bab43da8fb5faf5f254574462f73f260f85f7987f32154ff17" +checksum = "7afb4b54b8910cf5447638cb54bf4e8a65cbedd783af98b98c62ffe91f185543" dependencies = [ "libc", "zstd-sys", @@ -3317,9 +3540,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.5+zstd.1.5.2" +version = "2.0.7+zstd.1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc50ffce891ad571e9f9afe5039c4837bede781ac4bb13052ed7ae695518596" +checksum = "94509c3ba2fe55294d752b79842c530ccfab760192521df74a081a78d2b3c7f5" dependencies = [ "cc", "libc", diff --git a/backend/Cargo.toml b/bvplan/Cargo.toml similarity index 83% rename from backend/Cargo.toml rename to bvplan/Cargo.toml index 7efa3d7..3da28c2 100644 --- a/backend/Cargo.toml +++ b/bvplan/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "backend" +name = "bvplan" version = "0.1.0" edition = "2021" [[bin]] -name = "api" +name = "web" [[bin]] name = "worker" @@ -16,21 +16,24 @@ strip = true panic = "abort" [dependencies] -actix-cors = "0.6.4" actix-web = "4.2.1" anyhow = { version = "1.0.66", features = ["backtrace"] } +askama = { version = "0.11.1", features = ["serde-json"] } celery = { git = "https://github.com/rusty-celery/rusty-celery.git", branch = "main" } chrono = "0.4.23" diesel = { version = "2.0.2", features = ["i-implement-a-third-party-backend-and-opt-into-breaking-changes", "postgres", "chrono", "r2d2"] } env_logger = "0.9.3" envconfig = "0.10.0" +fancy-regex = "0.11.0" +flate2 = "1.0.25" lazy_static = "1.4.0" log = "0.4.17" +minify-html = "0.10.8" r2d2_redis = "0.14.0" -regex = "1.7.1" reqwest = "0.11.13" scraper = "0.14.0" serde = "1.0.148" +serde_json = "1.0.93" stdext = "0.3.1" tokio = { version = "1.22.0", features = ["full"] } untis = { git = "https://git.dergrimm.net/dergrimm/untis.rs.git", branch = "main" } diff --git a/backend/Dockerfile b/bvplan/Dockerfile similarity index 69% rename from backend/Dockerfile rename to bvplan/Dockerfile index b775a3d..6440809 100644 --- a/backend/Dockerfile +++ b/bvplan/Dockerfile @@ -13,10 +13,16 @@ FROM chef as builder WORKDIR /usr/src/backend COPY --from=planner /usr/src/backend/recipe.json . RUN cargo chef cook --recipe-path recipe.json +COPY ./templates ./templates COPY ./src ./src RUN cargo build FROM docker.io/debian:bullseye-slim as runner +LABEL maintainer="Dominic Grimm " \ + org.opencontainers.image.description="Bessrer Vertretungsplan" \ + org.opencontainers.image.licenses="GPLv3" \ + org.opencontainers.image.source="https://git.dergrimm.net/dergrimm/bvplan" \ + org.opencontainers.image.url="https://git.dergrimm.net/dergrimm/bvplan" RUN apt update RUN apt install -y libpq5 RUN apt install -y ca-certificates @@ -29,6 +35,6 @@ WORKDIR /usr/src/backend COPY ./run.sh . RUN chmod +x ./run.sh COPY ./migrations ./migrations -COPY --from=builder /usr/src/backend/target/debug/api /usr/src/backend/target/debug/worker ./bin/ +COPY --from=builder /usr/src/backend/target/debug/web /usr/src/backend/target/debug/worker ./bin/ EXPOSE 80 ENTRYPOINT [ "./run.sh" ] diff --git a/backend/migrations/2022-12-03-124501_init/down.sql b/bvplan/migrations/2022-12-03-124501_init/down.sql similarity index 86% rename from backend/migrations/2022-12-03-124501_init/down.sql rename to bvplan/migrations/2022-12-03-124501_init/down.sql index 961c356..4bba7f9 100644 --- a/backend/migrations/2022-12-03-124501_init/down.sql +++ b/bvplan/migrations/2022-12-03-124501_init/down.sql @@ -18,6 +18,10 @@ DROP TABLE timegrid_time_unit; DROP TABLE timegrid_days; +DROP TYPE day_of_week; + +DROP INDEX timegrids_active; + DROP TABLE timegrids; DROP TABLE holidays; @@ -36,4 +40,6 @@ DROP INDEX tenants_active; DROP TABLE tenants; +DROP INDEX schoolyears_active; + DROP TABLE schoolyears; diff --git a/backend/migrations/2022-12-03-124501_init/up.sql b/bvplan/migrations/2022-12-03-124501_init/up.sql similarity index 91% rename from backend/migrations/2022-12-03-124501_init/up.sql rename to bvplan/migrations/2022-12-03-124501_init/up.sql index 49ae6d2..a8a36a5 100644 --- a/backend/migrations/2022-12-03-124501_init/up.sql +++ b/bvplan/migrations/2022-12-03-124501_init/up.sql @@ -7,7 +7,7 @@ CREATE TABLE schoolyears ( updated_at TIMESTAMP ); -CREATE UNIQUE INDEX schoolyears_unique ON schoolyears(active) +CREATE UNIQUE INDEX schoolyears_active ON schoolyears(active) WHERE active; @@ -92,14 +92,23 @@ CREATE TABLE holidays ( CREATE TABLE timegrids ( id SERIAL PRIMARY KEY, schoolyear_id INTEGER NOT NULL REFERENCES schoolyears(id), + active BOOLEAN NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP ); +CREATE UNIQUE INDEX timegrids_active ON timegrids(active) +WHERE + active; + +CREATE TYPE weekday AS ENUM ( + 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun' +); + CREATE TABLE timegrid_days ( id SERIAL PRIMARY KEY, timegrid_id INTEGER NOT NULL REFERENCES timegrids(id), - day_index SMALLINT NOT NULL, + weekday weekday NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP ); @@ -160,6 +169,7 @@ CREATE TABLE substitution_classes ( substitution_id INTEGER NOT NULL REFERENCES substitutions(id), position SMALLINT NOT NULL, class_id INTEGER NOT NULL REFERENCES classes(id), + original_id INTEGER REFERENCES classes(id), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP ); @@ -169,6 +179,7 @@ CREATE TABLE substitution_teachers ( substitution_id INTEGER NOT NULL REFERENCES substitutions(id), position SMALLINT NOT NULL, teacher_id INTEGER REFERENCES teachers(id), + original_id INTEGER REFERENCES teachers(id), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP ); @@ -178,6 +189,7 @@ CREATE TABLE substitution_subjects ( substitution_id INTEGER NOT NULL REFERENCES substitutions(id), position SMALLINT NOT NULL, subject_id INTEGER NOT NULL REFERENCES subjects(id), + original_id INTEGER REFERENCES subjects(id), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP ); @@ -187,7 +199,7 @@ CREATE TABLE substitution_rooms ( substitution_id INTEGER NOT NULL REFERENCES substitutions(id), position SMALLINT NOT NULL, room_id INTEGER REFERENCES rooms(id), - original_room_id INTEGER REFERENCES rooms(id), + original_id INTEGER REFERENCES rooms(id), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP ); diff --git a/backend/run.sh b/bvplan/run.sh similarity index 67% rename from backend/run.sh rename to bvplan/run.sh index 90758a2..6aac1a0 100644 --- a/backend/run.sh +++ b/bvplan/run.sh @@ -3,5 +3,6 @@ DATABASE_URL="$BACKEND_DB_URL" diesel setup \ --migration-dir ./migrations \ - --locked-schema && - RUST_BACKTRACE=full "./bin/$1" + --locked-schema + +RUST_BACKTRACE=1 RUST_LOG=info "./bin/$1" diff --git a/bvplan/src/bin/web.rs b/bvplan/src/bin/web.rs new file mode 100644 index 0000000..e3f46ba --- /dev/null +++ b/bvplan/src/bin/web.rs @@ -0,0 +1,48 @@ +#[cfg(not(target_env = "msvc"))] +use tikv_jemallocator::Jemalloc; + +#[cfg(not(target_env = "msvc"))] +#[global_allocator] +static GLOBAL: Jemalloc = Jemalloc; + +use actix_web::{get, middleware, web::Data, App, HttpResponse, HttpServer}; +use r2d2_redis::redis; +use std::ops::DerefMut; + +use bvplan::*; + +#[get("/")] +async fn index(redis_pool: Data) -> HttpResponse { + let redis_conn = &mut match redis_pool.get() { + Ok(x) => x, + Err(_) => return HttpResponse::InternalServerError().finish(), + }; + + if let Some(html) = match redis::cmd("GET") + .arg(cache::keys::SUBSTITUTIONS_HTML) + .query::>(redis_conn.deref_mut()) + { + Ok(x) => x, + Err(_) => return HttpResponse::InternalServerError().finish(), + } { + HttpResponse::Accepted() + .content_type("text/html; charset=utf-8") + .body(html) + } else { + HttpResponse::InternalServerError().finish() + } +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + env_logger::init(); + + let server = HttpServer::new(move || { + App::new() + .app_data(Data::new(cache::pool().unwrap())) + .wrap(middleware::Compress::default()) + .wrap(middleware::Logger::default()) + .service(index) + }); + server.bind("0.0.0.0:80").unwrap().run().await +} diff --git a/backend/src/bin/worker.rs b/bvplan/src/bin/worker.rs similarity index 73% rename from backend/src/bin/worker.rs rename to bvplan/src/bin/worker.rs index 2911956..5b067e0 100644 --- a/backend/src/bin/worker.rs +++ b/bvplan/src/bin/worker.rs @@ -7,18 +7,17 @@ static GLOBAL: Jemalloc = Jemalloc; use std::thread; +use bvplan::*; + #[tokio::main] async fn main() { - std::env::set_var("RUST_LOG", "info"); env_logger::init(); println!("Starting worker!"); - - backend::worker::init_blocking(); - + worker::init_blocking(); thread::spawn(|| { tokio::runtime::Runtime::new().unwrap().block_on(async { - backend::worker::beat() + worker::beat() .await .unwrap() .start() @@ -27,11 +26,10 @@ async fn main() { }); }); - let app = backend::worker::APP.lock().unwrap(); + let app = worker::APP.lock().unwrap(); let app = app.as_ref().unwrap(); - app.display_pretty().await; - app.consume_from(&[backend::worker::QUEUE_NAME]) + app.consume_from(&[worker::QUEUE_NAME]) .await .unwrap(); } diff --git a/bvplan/src/cache.rs b/bvplan/src/cache.rs new file mode 100644 index 0000000..a752608 --- /dev/null +++ b/bvplan/src/cache.rs @@ -0,0 +1,77 @@ +use anyhow::Result; + +use lazy_static::lazy_static; +use r2d2_redis::{r2d2, redis, RedisConnectionManager}; + +use crate::config; +use crate::db; + +pub type RedisPool = r2d2::Pool; +pub type ConnectionPool = r2d2::PooledConnection; +pub type Connection = redis::Connection; + +pub fn establish_connection() -> Result { + Ok(RedisConnectionManager::new( + config::CONFIG.redis_url.as_str(), + )?) +} + +pub fn pool() -> Result { + Ok(r2d2::Pool::builder().build(establish_connection()?)?) +} + +lazy_static! { + pub static ref POOL: RedisPool = pool().unwrap(); +} + +pub mod keys { + use super::*; + + // pub const SUBSTITUTIONS: &str = "substs"; + pub const SUBSTITUTIONS_HTML: &str = "substs_html"; + + pub mod substitutions { + use chrono::prelude::*; + use serde::{Deserialize, Serialize}; + + use super::*; + + #[derive(Deserialize, Serialize, Debug)] + pub struct Substitution { + #[serde(rename = "tm")] + pub time: (usize, Option), + #[serde(rename = "cl")] + pub classes: Vec, + #[serde(rename = "ps")] + pub prev_subject: String, + #[serde(rename = "s")] + pub subject: Option, + #[serde(rename = "t")] + pub teachers: Vec, + #[serde(rename = "pr")] + pub prev_room: Option, + #[serde(rename = "r")] + pub room: Option, + #[serde(rename = "tx")] + pub text: Option, + } + + #[derive(Deserialize, Serialize, Debug)] + pub struct SubstitutionQuery { + #[serde(rename = "d")] + pub date: NaiveDate, + #[serde(rename = "w")] + pub week_type: db::models::WeekType, + #[serde(rename = "q")] + pub queried_at: NaiveDateTime, + #[serde(rename = "i")] + pub last_import_time: NaiveDateTime, + #[serde(rename = "y")] + pub schoolyear: String, + #[serde(rename = "t")] + pub tenant: String, + #[serde(rename = "s")] + pub substitutions: Vec, + } + } +} diff --git a/backend/src/config.rs b/bvplan/src/config.rs similarity index 90% rename from backend/src/config.rs rename to bvplan/src/config.rs index c75f961..d04fa39 100644 --- a/backend/src/config.rs +++ b/bvplan/src/config.rs @@ -48,11 +48,12 @@ lazy_static! { pub fn untis_from_env() -> Result { Ok(untis::Client { - api_url: Url::parse(&CONFIG.untis_api_url)?, + // api_url: Url::parse(&CONFIG.untis_api_url)?, + webuntis_url: Url::parse(&CONFIG.untis_api_url)?, rpc_url: untis::Client::gen_rpc_url( - Url::parse(&CONFIG.untis_rpc_url)?, + &Url::parse(&CONFIG.untis_rpc_url)?, &CONFIG.untis_school, - ), + )?, client_name: CONFIG.untis_client_name.to_owned(), user_agent: "Mozilla/5.0 (Windows NT 10.0; rv:108.0) Gecko/20100101 Firefox/108.0" .to_string(), diff --git a/backend/src/db/mod.rs b/bvplan/src/db/mod.rs similarity index 90% rename from backend/src/db/mod.rs rename to bvplan/src/db/mod.rs index 53fa54b..63ff31e 100644 --- a/backend/src/db/mod.rs +++ b/bvplan/src/db/mod.rs @@ -10,8 +10,11 @@ pub mod models; pub mod schema; pub type DbPool = Pool>; +pub type Connection = PgConnection; pub fn establish_connection() -> ConnectionResult { + use diesel::Connection; + PgConnection::establish(&config::CONFIG.db_url) } diff --git a/backend/src/db/models.rs b/bvplan/src/db/models.rs similarity index 73% rename from backend/src/db/models.rs rename to bvplan/src/db/models.rs index 75fe6b8..5fe1480 100644 --- a/backend/src/db/models.rs +++ b/bvplan/src/db/models.rs @@ -1,6 +1,10 @@ +use anyhow::bail; use chrono::prelude::*; use diesel::prelude::*; +use serde::{Deserialize, Serialize}; +use std::fmt::Display; use std::io::Write; +use std::str::FromStr; use crate::db::schema; @@ -160,8 +164,8 @@ pub struct NewDepartment<'a> { } #[derive(Identifiable, Queryable, Associations, Debug)] -#[diesel(table_name = schema::holidays)] #[diesel(belongs_to(Schoolyear))] +#[diesel(table_name = schema::holidays)] pub struct Holiday { pub id: i32, pub untis_id: i32, @@ -185,13 +189,175 @@ pub struct NewHoliday<'a> { pub end_date: NaiveDate, } +#[derive(Identifiable, Queryable, Associations, Debug)] +#[diesel(belongs_to(Schoolyear))] +#[diesel(table_name = schema::timegrids)] +pub struct Timegrid { + pub id: i32, + pub schoolyear_id: i32, + pub active: bool, + pub created_at: NaiveDateTime, + pub updated_at: Option, +} + +#[derive(Insertable, Debug)] +#[diesel(table_name = schema::timegrids)] +pub struct NewTimegrid { + pub schoolyear_id: i32, + pub active: bool, +} + #[derive(diesel::FromSqlRow, diesel::AsExpression, PartialEq, Eq, Clone, Debug)] +#[diesel(sql_type = schema::sql_types::Weekday)] +pub enum Weekday { + Mon, + Tue, + Wed, + Thu, + Fri, + Sat, + Sun, +} + +impl TryFrom for Weekday { + type Error = anyhow::Error; + + fn try_from(value: u8) -> Result { + Ok(match value { + 1 => Self::Sun, + 2 => Self::Mon, + 3 => Self::Tue, + 4 => Self::Wed, + 5 => Self::Thu, + 6 => Self::Fri, + 7 => Self::Sat, + _ => bail!("Invalid weekday: {:?}", value), + }) + } +} + +impl From for Weekday { + fn from(value: chrono::Weekday) -> Self { + match value { + chrono::Weekday::Mon => Self::Mon, + chrono::Weekday::Tue => Self::Tue, + chrono::Weekday::Wed => Self::Wed, + chrono::Weekday::Thu => Self::Thu, + chrono::Weekday::Fri => Self::Fri, + chrono::Weekday::Sat => Self::Sat, + chrono::Weekday::Sun => Self::Sun, + } + } +} + +impl diesel::serialize::ToSql for Weekday { + fn to_sql<'b>( + &'b self, + out: &mut diesel::serialize::Output<'b, '_, diesel::pg::Pg>, + ) -> diesel::serialize::Result { + match *self { + Self::Mon => out.write_all(b"mon")?, + Self::Tue => out.write_all(b"tue")?, + Self::Wed => out.write_all(b"wed")?, + Self::Thu => out.write_all(b"thu")?, + Self::Fri => out.write_all(b"fri")?, + Self::Sat => out.write_all(b"sat")?, + Self::Sun => out.write_all(b"sun")?, + } + + Ok(diesel::serialize::IsNull::No) + } +} + +impl diesel::deserialize::FromSql for Weekday { + fn from_sql( + bytes: diesel::backend::RawValue<'_, diesel::pg::Pg>, + ) -> diesel::deserialize::Result { + match bytes.as_bytes() { + b"mon" => Ok(Self::Mon), + b"tue" => Ok(Self::Tue), + b"wed" => Ok(Self::Wed), + b"thu" => Ok(Self::Thu), + b"fri" => Ok(Self::Fri), + b"sat" => Ok(Self::Sat), + b"sun" => Ok(Self::Sun), + _ => Err("Unrecognized enum variant".into()), + } + } +} + +#[derive(Identifiable, Queryable, Associations, Debug)] +#[diesel(belongs_to(Timegrid))] +#[diesel(table_name = schema::timegrid_days)] +pub struct TimegridDay { + pub id: i32, + pub timegrid_id: i32, + pub weekday: Weekday, + pub created_at: NaiveDateTime, + pub updated_at: Option, +} + +#[derive(Insertable, Debug)] +#[diesel(table_name = schema::timegrid_days)] +pub struct NewTimegridDay { + pub timegrid_id: i32, + pub weekday: Weekday, +} + +#[derive(Identifiable, Queryable, Associations, Debug)] +#[diesel(belongs_to(TimegridDay))] +#[diesel(table_name = schema::timegrid_time_unit)] +pub struct TimegridTimeUnit { + pub id: i32, + pub timegrid_day_id: i32, + pub start_time: NaiveTime, + pub end_time: NaiveTime, + pub created_at: NaiveDateTime, + pub updated_at: Option, +} + +#[derive(Insertable, Debug)] +#[diesel(table_name = schema::timegrid_time_unit)] +pub struct NewTimegridTimeUnit { + pub timegrid_day_id: i32, + pub start_time: NaiveTime, + pub end_time: NaiveTime, +} + +#[derive( + Serialize, Deserialize, diesel::FromSqlRow, diesel::AsExpression, PartialEq, Eq, Clone, Debug, +)] #[diesel(sql_type = schema::sql_types::WeekType)] pub enum WeekType { A, B, } +impl Display for WeekType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::A => "A", + Self::B => "B", + } + ) + } +} + +impl FromStr for WeekType { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s { + "A" => Ok(Self::A), + "B" => Ok(Self::B), + _ => bail!("Unrecognized enum variant"), + } + } +} + impl diesel::serialize::ToSql for WeekType { fn to_sql<'b>( &'b self, @@ -373,6 +539,7 @@ pub struct SubstitutionClass { pub substitution_id: i32, pub position: i16, pub class_id: i32, + pub original_id: Option, pub created_at: NaiveDateTime, pub updated_at: Option, } @@ -383,6 +550,7 @@ pub struct NewSubstitutionClass { pub substitution_id: i32, pub position: i16, pub class_id: i32, + pub original_id: Option, } #[derive(Identifiable, Queryable, Associations, Debug)] @@ -394,6 +562,7 @@ pub struct SubstitutionTeacher { pub substitution_id: i32, pub position: i16, pub teacher_id: Option, + pub original_id: Option, pub created_at: NaiveDateTime, pub updated_at: Option, } @@ -404,6 +573,7 @@ pub struct NewSubstitutionTeacher { pub substitution_id: i32, pub position: i16, pub teacher_id: Option, + pub original_id: Option, } #[derive(Identifiable, Queryable, Associations, Debug)] @@ -415,6 +585,7 @@ pub struct SubstitutionSubject { pub substitution_id: i32, pub position: i16, pub subject_id: i32, + pub original_id: Option, pub created_at: NaiveDateTime, pub updated_at: Option, } @@ -425,6 +596,7 @@ pub struct NewSubstitutionSubject { pub substitution_id: i32, pub position: i16, pub subject_id: i32, + pub original_id: Option, } #[derive(Identifiable, Queryable, Associations, Debug)] @@ -435,9 +607,8 @@ pub struct SubstitutionRoom { pub id: i32, pub substitution_id: i32, pub position: i16, - pub index: i16, pub room_id: Option, - pub original_room_id: Option, + pub original_id: Option, pub created_at: NaiveDateTime, pub updated_at: Option, } @@ -448,5 +619,5 @@ pub struct NewSubstitutionRoom { pub substitution_id: i32, pub position: i16, pub room_id: Option, - pub original_room_id: Option, + pub original_id: Option, } diff --git a/backend/src/db/schema.rs b/bvplan/src/db/schema.rs similarity index 90% rename from backend/src/db/schema.rs rename to bvplan/src/db/schema.rs index 4a16323..4a9c322 100644 --- a/backend/src/db/schema.rs +++ b/bvplan/src/db/schema.rs @@ -1,4 +1,8 @@ pub mod sql_types { + #[derive(diesel::sql_types::SqlType)] + #[diesel(postgres_type(name = "weekday"))] + pub struct Weekday; + #[derive(diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "substitution_type"))] pub struct SubstitutionType; @@ -111,17 +115,21 @@ diesel::table! { timegrids { id -> Integer, schoolyear_id -> Integer, + active -> Bool, created_at -> Timestamp, updated_at -> Nullable, } } diesel::table! { + use diesel::sql_types::*; + + use super::sql_types::*; + timegrid_days { id -> Integer, timegrid_id -> Integer, - #[sql_name = "day_index"] - day -> SmallInt, + weekday -> Weekday, created_at -> Timestamp, updated_at -> Nullable, } @@ -130,7 +138,7 @@ diesel::table! { diesel::table! { timegrid_time_unit { id -> Integer, - timegrid_id -> Integer, + timegrid_day_id -> Integer, start_time -> Time, end_time -> Time, created_at -> Timestamp, @@ -141,7 +149,7 @@ diesel::table! { diesel::table! { use diesel::sql_types::*; - use super::sql_types::WeekType; + use super::sql_types::*; substitution_queries { id -> Integer, @@ -157,7 +165,7 @@ diesel::table! { diesel::table! { use diesel::sql_types::*; - use super::sql_types::SubstitutionType; + use super::sql_types::*; substitutions { id -> Integer, @@ -178,6 +186,7 @@ diesel::table! { substitution_id -> Integer, position -> SmallInt, class_id -> Integer, + original_id -> Nullable, created_at -> Timestamp, updated_at -> Nullable, } @@ -189,6 +198,7 @@ diesel::table! { substitution_id -> Integer, position -> SmallInt, teacher_id -> Nullable, + original_id -> Nullable, created_at -> Timestamp, updated_at -> Nullable, } @@ -200,6 +210,7 @@ diesel::table! { substitution_id -> Integer, position -> SmallInt, subject_id -> Integer, + original_id -> Nullable, created_at -> Timestamp, updated_at -> Nullable, } @@ -211,7 +222,7 @@ diesel::table! { substitution_id -> Integer, position -> SmallInt, room_id -> Nullable, - original_room_id -> Nullable, + original_id -> Nullable, created_at -> Timestamp, updated_at -> Nullable, } diff --git a/backend/src/lib.rs b/bvplan/src/lib.rs similarity index 75% rename from backend/src/lib.rs rename to bvplan/src/lib.rs index 0b9a222..1c0ef45 100644 --- a/backend/src/lib.rs +++ b/bvplan/src/lib.rs @@ -1,4 +1,5 @@ pub mod cache; pub mod config; pub mod db; +pub mod templates; pub mod worker; diff --git a/bvplan/src/templates.rs b/bvplan/src/templates.rs new file mode 100644 index 0000000..b8d7e66 --- /dev/null +++ b/bvplan/src/templates.rs @@ -0,0 +1,9 @@ +use askama::Template; + +use crate::cache; + +#[derive(Template)] +#[template(path = "bvplan.html")] +pub struct BVplan { + pub data: cache::keys::substitutions::SubstitutionQuery, +} diff --git a/backend/src/worker/mod.rs b/bvplan/src/worker/mod.rs similarity index 73% rename from backend/src/worker/mod.rs rename to bvplan/src/worker/mod.rs index 50776f6..baa771f 100644 --- a/backend/src/worker/mod.rs +++ b/bvplan/src/worker/mod.rs @@ -1,19 +1,19 @@ use celery::beat::DeltaSchedule; -use celery::broker::AMQPBroker; use lazy_static::lazy_static; use std::sync::{Arc, Mutex}; use std::thread; -use std::time; +use std::time::Duration; use stdext::duration::DurationExt; use crate::config; pub mod update_info; +pub mod update_meta; pub const QUEUE_NAME: &str = "celery"; lazy_static! { - pub static ref APP: Mutex>>> = Mutex::new(None); + pub static ref APP: Mutex>> = Mutex::new(None); } #[tokio::main] @@ -23,6 +23,7 @@ pub async fn init() { broker = AMQPBroker { &config::CONFIG.amqp_url }, tasks = [ update_info::update_info, + update_meta::update_meta, ], task_routes = [ "*" => QUEUE_NAME, @@ -35,12 +36,38 @@ pub async fn init() { ); } +pub fn beat() -> impl std::future::Future< + Output = Result< + celery::beat::Beat, + celery::error::BeatError, + >, +> { + celery::beat!( + broker = AMQPBroker { &config::CONFIG.amqp_url }, + tasks = [ + "update_info" => { + update_info::update_info, + schedule = DeltaSchedule::new(Duration::from_minutes(999)), + args = (), + }, + "update_meta" => { + update_meta::update_meta, + schedule = DeltaSchedule::new(Duration::from_hours(6)), + args = (), + } + ], + task_routes = [ + "*" => QUEUE_NAME, + ] + ) +} + pub fn init_blocking() { thread::spawn(|| { tokio::runtime::Runtime::new().unwrap().spawn_blocking(init); }); - let dur = time::Duration::from_secs(1); + let dur = Duration::from_secs(1); let mut i = 0; loop { if APP.lock().unwrap().is_some() { @@ -54,24 +81,3 @@ pub fn init_blocking() { } } } - -pub fn beat() -> impl std::future::Future< - Output = Result< - celery::beat::Beat, - celery::error::BeatError, - >, -> { - celery::beat!( - broker = AMQPBroker { &config::CONFIG.amqp_url }, - tasks = [ - "update_info" => { - update_info::update_info, - schedule = DeltaSchedule::new(time::Duration::from_minutes(5)), - args = (), - } - ], - task_routes = [ - "*" => QUEUE_NAME, - ] - ) -} diff --git a/backend/src/worker/update_info.rs b/bvplan/src/worker/update_info.rs similarity index 51% rename from backend/src/worker/update_info.rs rename to bvplan/src/worker/update_info.rs index e3db80e..2a29bce 100644 --- a/backend/src/worker/update_info.rs +++ b/bvplan/src/worker/update_info.rs @@ -1,49 +1,28 @@ use anyhow::{Context, Result}; +use askama::Template; use celery::error::TaskError; use celery::task::TaskResult; use chrono::prelude::*; use diesel::prelude::*; +use fancy_regex::Regex; use lazy_static::lazy_static; -use regex::Regex; +use r2d2_redis::redis; +use std::fs; +use std::io::Write; +use std::path::Path; use std::thread; use std::time::Duration; -use crate::{config, db}; +use crate::cache; +use crate::config; +use crate::db; +use crate::templates; -async fn fetch_schoolyears(client: &untis::Client, db_conn: &mut PgConnection) -> Result { - let existing_schoolyears = db::schema::schoolyears::table - .select(db::schema::schoolyears::untis_id) - .load::(db_conn)?; - diesel::insert_into(db::schema::schoolyears::table) - .values( - &client - .schoolyears() - .await? - .iter() - .filter(|y| !existing_schoolyears.contains(&y.id)) - .map(|y| db::models::NewSchoolyear { - untis_id: y.id, - name: &y.name, - active: false, - }) - .collect::>(), - ) - .execute(db_conn)?; - - let id = db::schema::schoolyears::table +async fn get_schoolyear(client: &untis::Client, db_conn: &mut db::Connection) -> Result { + Ok(db::schema::schoolyears::table .filter(db::schema::schoolyears::untis_id.eq(client.current_schoolyear().await?.id)) .select(db::schema::schoolyears::id) - .first(db_conn)?; - - diesel::update(db::schema::schoolyears::table) - .set(db::schema::schoolyears::active.eq(false)) - .execute(db_conn)?; - diesel::update(db::schema::schoolyears::table) - .filter(db::schema::schoolyears::id.eq(id)) - .set(db::schema::schoolyears::active.eq(true)) - .execute(db_conn)?; - - Ok(id) + .first(db_conn)?) } async fn fetch_current_tenant( @@ -95,6 +74,50 @@ async fn fetch_current_tenant( Ok(()) } +async fn fetch_timegrid( + client: &untis::Client, + db_conn: &mut PgConnection, + schoolyear_id: i32, +) -> Result<()> { + let days = client.timegrid().await?; + + diesel::update(db::schema::timegrids::table) + .filter(db::schema::timegrids::active) + .set(db::schema::timegrids::active.eq(false)) + .execute(db_conn)?; + let timegrid_id = diesel::insert_into(db::schema::timegrids::table) + .values(db::models::NewTimegrid { + schoolyear_id, + active: true, + }) + .returning(db::schema::timegrids::id) + .get_result::(db_conn)?; + + for day in days { + let timegrid_day_id = diesel::insert_into(db::schema::timegrid_days::table) + .values(db::models::NewTimegridDay { + timegrid_id, + weekday: day.day.try_into()?, + }) + .returning(db::schema::timegrid_days::id) + .get_result::(db_conn)?; + diesel::insert_into(db::schema::timegrid_time_unit::table) + .values( + day.time_units + .into_iter() + .map(|x| db::models::NewTimegridTimeUnit { + timegrid_day_id, + start_time: x.start_time, + end_time: x.end_time, + }) + .collect::>(), + ) + .execute(db_conn)?; + } + + Ok(()) +} + async fn fetch_teachers( client: &untis::Client, db_conn: &mut PgConnection, @@ -281,11 +304,10 @@ async fn fetch_substitutions( client: &untis::Client, db_conn: &mut PgConnection, schoolyear_id: i32, -) -> Result<()> { +) -> Result { lazy_static! { static ref TITLE_SELECTOR: scraper::Selector = scraper::Selector::parse(".mon_title").unwrap(); - static ref DATE_REGEX: Regex = Regex::new(r"([^\s]+)").unwrap(); static ref WEEK_TYPE_REGEX: Regex = Regex::new(r"\bWoche\s+\K\S+").unwrap(); } @@ -313,35 +335,23 @@ async fn fetch_substitutions( .text() .next() .context("\"mon_title\" element is empty")?; - dbg!(title); - dbg!(DATE_REGEX.captures(title)); - dbg!(WEEK_TYPE_REGEX.captures(title)); ( - chrono::NaiveDate::parse_from_str("18.1.2023", "%d.%m.%Y")?, - db::models::WeekType::A, + NaiveDate::parse_from_str( + title + .split_once(' ') + .context("Could not split title string by whitespace")? + .0, + "%d.%m.%Y", + )?, + WEEK_TYPE_REGEX + .captures(title)? + .context("No week type could be found")? + .get(0) + .context("No week type could be found")? + .as_str() + .parse::()?, ) - - // ( - // chrono::NaiveDate::parse_from_str( - // title - // .0 - // .split_whitespace() - // .next() - // .context("Could not find date")?, - // "%d.%m.%Y", - // )?, - // match title - // .1 - // .split_whitespace() - // .last() - // .context("Could not find week type indicator")? - // { - // "A" => db::models::WeekType::A, - // "B" => db::models::WeekType::B, - // x => bail!("Invalid week type: {:?}", x), - // }, - // ) }; let substitution_query_id = diesel::insert_into(db::schema::substitution_queries::table) @@ -381,6 +391,16 @@ async fn fetch_substitutions( .filter(db::schema::classes::untis_id.eq(c.id)) .select(db::schema::classes::id) .get_result::(db_conn)?, + original_id: if let Some(original_id) = c.original_id { + Some( + db::schema::classes::table + .filter(db::schema::classes::untis_id.eq(original_id)) + .select(db::schema::classes::id) + .get_result::(db_conn)?, + ) + } else { + None + }, }) }) .collect::>>()?, @@ -407,6 +427,16 @@ async fn fetch_substitutions( .get_result::(db_conn)?, ) }, + original_id: if let Some(original_id) = t.original_id { + Some( + db::schema::teachers::table + .filter(db::schema::teachers::untis_id.eq(original_id)) + .select(db::schema::teachers::id) + .get_result::(db_conn)?, + ) + } else { + None + }, }) }) .collect::>>()?, @@ -427,6 +457,16 @@ async fn fetch_substitutions( .filter(db::schema::subjects::untis_id.eq(s.id)) .select(db::schema::subjects::id) .get_result::(db_conn)?, + original_id: if let Some(original_id) = s.original_id { + Some( + db::schema::subjects::table + .filter(db::schema::subjects::untis_id.eq(original_id)) + .select(db::schema::subjects::id) + .get_result::(db_conn)?, + ) + } else { + None + }, }) }) .collect::>>()?, @@ -453,7 +493,7 @@ async fn fetch_substitutions( .get_result::(db_conn)?, ) }, - original_room_id: if let Some(original_id) = r.original_id { + original_id: if let Some(original_id) = r.original_id { Some( db::schema::rooms::table .filter(db::schema::rooms::untis_id.eq(original_id)) @@ -470,6 +510,209 @@ async fn fetch_substitutions( .execute(db_conn)?; } + Ok(substitution_query_id) +} + +type StartEndTime = (NaiveTime, NaiveTime); + +fn get_period(times: &Vec, start: bool, time: NaiveTime) -> Option { + times + .iter() + .position(|x| (if start { x.0 } else { x.1 }) == time) + .map(|x| x + 1) +} + +fn cache_substitutions( + db_conn: &mut PgConnection, + redis_conn: &mut cache::Connection, + substitution_query_id: i32, + last_import_time: NaiveDateTime, +) -> Result<()> { + let (queried_at, date, week_type) = db::schema::substitution_queries::table + .select(( + db::schema::substitution_queries::queried_at, + db::schema::substitution_queries::date, + db::schema::substitution_queries::week_type, + )) + .filter(db::schema::substitution_queries::id.eq(substitution_query_id)) + .first::<(NaiveDateTime, NaiveDate, db::models::WeekType)>(db_conn)?; + let weekday: db::models::Weekday = date.weekday().into(); + + let timegrid_id = db::schema::timegrids::table + .select(db::schema::timegrids::id) + .filter(db::schema::timegrids::active) + .first::(db_conn)?; + let day_id: i32 = db::schema::timegrid_days::table + .select(( + db::schema::timegrid_days::id, + db::schema::timegrid_days::weekday, + )) + .filter(db::schema::timegrid_days::timegrid_id.eq(timegrid_id)) + .load::<(i32, db::models::Weekday)>(db_conn)? + .into_iter() + .find(|x| x.1 == weekday) + .context("Could not find timegrid day")? + .0; + let times: Vec = db::schema::timegrid_time_unit::table + .select(( + db::schema::timegrid_time_unit::start_time, + db::schema::timegrid_time_unit::end_time, + )) + .filter(db::schema::timegrid_time_unit::timegrid_day_id.eq(day_id)) + .order(db::schema::timegrid_time_unit::start_time.asc()) + .load::(db_conn)?; + + let query = cache::keys::substitutions::SubstitutionQuery { + date, + week_type, + queried_at, + last_import_time, + schoolyear: "schoolyear".to_string(), + tenant: "OHG Furtwangen".to_string(), + substitutions: db::schema::substitutions::table + .filter(db::schema::substitutions::substitution_query_id.eq(substitution_query_id)) + .load::(db_conn) + .unwrap() + .into_iter() + .map(|s| { + if let Some(subst_subject) = db::schema::substitution_subjects::table + .filter(db::schema::substitution_subjects::substitution_id.eq(s.id)) + .order(db::schema::substitution_subjects::position.asc()) + .first::(db_conn) + .optional()? + { + let subst_class_ids = db::schema::substitution_classes::table + .select(db::schema::substitution_classes::class_id) + .filter(db::schema::substitution_classes::substitution_id.eq(s.id)) + .order(db::schema::substitution_classes::position.asc()) + .load::(db_conn)?; + let classes = db::schema::classes::table + .select(db::schema::classes::name) + .filter(db::schema::classes::id.eq_any(subst_class_ids)) + .load::(db_conn)?; + + let subst_teachers = db::schema::substitution_teachers::table + .filter(db::schema::substitution_teachers::substitution_id.eq(s.id)) + .order(db::schema::substitution_teachers::position.asc()) + .load::(db_conn)?; + + let (prev_room, room) = if let Some(r) = db::schema::substitution_rooms::table + .filter(db::schema::substitution_rooms::substitution_id.eq(s.id)) + .order(db::schema::substitution_rooms::position.asc()) + .first::(db_conn) + .optional()? + { + let name = if let Some(id) = r.room_id { + Some( + db::schema::rooms::table + .select(db::schema::rooms::name) + .filter(db::schema::rooms::id.eq(id)) + .first::(db_conn)?, + ) + } else { + None + }; + + match s.subst_type { + db::models::SubstitutionType::Cancel => (name, None), + _ => ( + if let Some(id) = r.original_id { + Some( + db::schema::rooms::table + .select(db::schema::rooms::name) + .filter(db::schema::rooms::id.eq(id)) + .first::(db_conn)?, + ) + } else { + None + }, + name, + ), + } + } else { + (None, None) + }; + + let prev_subject = db::schema::subjects::table + .select(db::schema::subjects::name) + .filter(db::schema::subjects::id.eq(subst_subject.subject_id)) + .first::(db_conn)?; + + let start_period = get_period(×, true, s.start_time) + .context("Could not find period from start time")?; + + Ok(Some(cache::keys::substitutions::Substitution { + time: (start_period, None), + classes, + prev_subject: prev_subject.to_owned(), + subject: match s.subst_type { + db::models::SubstitutionType::Cancel => None, + _ => match subst_subject.original_id { + Some(id) => Some( + db::schema::subjects::table + .select(db::schema::subjects::name) + .filter(db::schema::subjects::id.eq(id)) + .first::(db_conn)?, + ), + None => Some(prev_subject), + }, + }, + teachers: match s.subst_type { + db::models::SubstitutionType::Cancel => vec![], + _ => db::schema::teachers::table + .select(db::schema::teachers::display_name) + .filter( + db::schema::teachers::id.eq_any( + subst_teachers + .iter() + .filter_map(|t| t.teacher_id) + .collect::>(), + ), + ) + .load::(db_conn)?, + }, + prev_room, + room, + text: s.text, + })) + } else { + Ok(None) + } + }) + .collect::>>() + .unwrap() + .into_iter() + .filter_map(|s| s) + .collect::>(), + }; + + fs::write( + Path::new("/backups").join(format!( + "{}_{}_{}.json.gz", + date, queried_at, substitution_query_id + )), + { + let mut e = flate2::write::ZlibEncoder::new(vec![], flate2::Compression::best()); + e.write_all(serde_json::to_string(&query)?.as_bytes())?; + e.finish()? + }, + )?; + + redis::cmd("SET") + .arg(cache::keys::SUBSTITUTIONS_HTML) + .arg(minify_html::minify( + templates::BVplan { data: query }.render()?.as_bytes(), + &minify_html::Cfg { + do_not_minify_doctype: false, + ensure_spec_compliant_unquoted_attribute_values: false, + keep_spaces_between_attributes: false, + minify_css: true, + minify_js: true, + ..Default::default() + }, + )) + .query::<()>(redis_conn)?; + Ok(()) } @@ -480,64 +723,80 @@ pub async fn update_info() -> TaskResult<()> { thread::sleep(dur); let mut client = match config::untis_from_env() { Ok(x) => x, - Err(e) => return Err(TaskError::UnexpectedError(e.to_string())), + Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))), }; if let Err(e) = client.login().await { - return Err(TaskError::UnexpectedError(e.to_string())); + return Err(TaskError::UnexpectedError(format!("{:?}", e))); } thread::sleep(dur); let db_conn = &mut match db::POOL.get() { Ok(x) => x, - Err(e) => return Err(TaskError::UnexpectedError(e.to_string())), + Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))), }; - // let redis_conn = &mut match cache::POOL.get() { - // Ok(x) => x, - // Err(e) => return Err(TaskError::UnexpectedError(e.to_string())), - // }; - - let schoolyear_id = match fetch_schoolyears(&client, db_conn).await { + let redis_conn = &mut match cache::POOL.get() { Ok(x) => x, - Err(e) => return Err(TaskError::UnexpectedError(e.to_string())), + Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))), + }; + + let schoolyear_id = match get_schoolyear(&client, db_conn).await { + Ok(x) => x, + Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))), }; thread::sleep(dur); if let Err(e) = fetch_current_tenant(&client, db_conn, schoolyear_id).await { - return Err(TaskError::UnexpectedError(e.to_string())); + return Err(TaskError::UnexpectedError(format!("{:?}", e))); + } + thread::sleep(dur); + if let Err(e) = fetch_timegrid(&client, db_conn, schoolyear_id).await { + return Err(TaskError::UnexpectedError(format!("{:?}", e))); } thread::sleep(dur); if let Err(e) = fetch_teachers(&client, db_conn, schoolyear_id).await { - return Err(TaskError::UnexpectedError(e.to_string())); + return Err(TaskError::UnexpectedError(format!("{:?}", e))); } thread::sleep(dur); if let Err(e) = fetch_classes(&client, db_conn, schoolyear_id).await { - return Err(TaskError::UnexpectedError(e.to_string())); + return Err(TaskError::UnexpectedError(format!("{:?}", e))); } thread::sleep(dur); if let Err(e) = fetch_subjects(&client, db_conn, schoolyear_id).await { - return Err(TaskError::UnexpectedError(e.to_string())); + return Err(TaskError::UnexpectedError(format!("{:?}", e))); } thread::sleep(dur); if let Err(e) = fetch_rooms(&client, db_conn, schoolyear_id).await { - return Err(TaskError::UnexpectedError(e.to_string())); + return Err(TaskError::UnexpectedError(format!("{:?}", e))); } thread::sleep(dur); if let Err(e) = fetch_departments(&client, db_conn, schoolyear_id).await { - return Err(TaskError::UnexpectedError(e.to_string())); + return Err(TaskError::UnexpectedError(format!("{:?}", e))); } thread::sleep(dur); if let Err(e) = fetch_holidays(&client, db_conn, schoolyear_id).await { - return Err(TaskError::UnexpectedError(e.to_string())); + return Err(TaskError::UnexpectedError(format!("{:?}", e))); } thread::sleep(dur); - if let Err(e) = fetch_substitutions(&client, db_conn, schoolyear_id).await { - return Err(TaskError::UnexpectedError(e.to_string())); - } + let last_import_time = match client.last_import_time().await { + Ok(x) => x, + Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))), + }; + thread::sleep(dur); + let substitution_query_id = match fetch_substitutions(&client, db_conn, schoolyear_id).await { + Ok(x) => x, + Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))), + }; thread::sleep(dur); if let Err(e) = client.logout().await { - return Err(TaskError::UnexpectedError(e.to_string())); + return Err(TaskError::UnexpectedError(format!("{:?}", e))); + } + // thread::sleep(dur); + + if let Err(e) = + cache_substitutions(db_conn, redis_conn, substitution_query_id, last_import_time) + { + return Err(TaskError::UnexpectedError(format!("{:?}", e))); } - thread::sleep(dur); Ok(()) } diff --git a/bvplan/src/worker/update_meta.rs b/bvplan/src/worker/update_meta.rs new file mode 100644 index 0000000..bed54d5 --- /dev/null +++ b/bvplan/src/worker/update_meta.rs @@ -0,0 +1,71 @@ +use anyhow::Result; +use celery::{error::TaskError, task::TaskResult}; +use diesel::prelude::*; +use std::thread; +use std::time::Duration; + +use crate::config; +use crate::db; + +async fn fetch_schoolyears(client: &untis::Client, db_conn: &mut db::Connection) -> Result { + let existing_schoolyears = db::schema::schoolyears::table + .select(db::schema::schoolyears::untis_id) + .load::(db_conn)?; + diesel::insert_into(db::schema::schoolyears::table) + .values( + &client + .schoolyears() + .await? + .iter() + .filter(|y| !existing_schoolyears.contains(&y.id)) + .map(|y| db::models::NewSchoolyear { + untis_id: y.id, + name: &y.name, + active: false, + }) + .collect::>(), + ) + .execute(db_conn)?; + + let id = db::schema::schoolyears::table + .filter(db::schema::schoolyears::untis_id.eq(client.current_schoolyear().await?.id)) + .select(db::schema::schoolyears::id) + .first(db_conn)?; + + diesel::update(db::schema::schoolyears::table) + .set(db::schema::schoolyears::active.eq(false)) + .execute(db_conn)?; + diesel::update(db::schema::schoolyears::table) + .filter(db::schema::schoolyears::id.eq(id)) + .set(db::schema::schoolyears::active.eq(true)) + .execute(db_conn)?; + + Ok(id) +} + +#[celery::task] +pub async fn update_meta() -> TaskResult<()> { + let dur = Duration::from_secs(2); + let mut client = match config::untis_from_env() { + Ok(x) => x, + Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))), + }; + let db_conn = &mut match db::POOL.get() { + Ok(x) => x, + Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))), + }; + thread::sleep(dur); + + let schoolyear_id = match fetch_schoolyears(&client, db_conn).await { + Ok(x) => x, + Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))), + }; + dbg!(&schoolyear_id); + thread::sleep(dur); + + if let Err(e) = client.logout().await { + return Err(TaskError::UnexpectedError(format!("{:?}", e))); + } + + Ok(()) +} diff --git a/bvplan/templates/bvplan.html b/bvplan/templates/bvplan.html new file mode 100644 index 0000000..ae90394 --- /dev/null +++ b/bvplan/templates/bvplan.html @@ -0,0 +1,287 @@ + + + + + + + BVplan | dergrimm.net + + + + + + + + + + +
+
+

BVplan

+ dergrimm.net +
+
+ {{ data.tenant }}, {{ data.schoolyear }} +
+ Stand: {{ data.queried_at }} +
+ Untis: {{ data.last_import_time }} +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% for subst in data.substitutions %} + + + + + + + + + + + {% endfor %} + +
+ {{ data.date }}, Woche {{ data.week_type }} +
StundeKlasse(n)(Fach)FachVertreter(Raum)RaumText
+ {% match subst.time.1 %} + {% when Some with (x) %} + {{ subst.time.0 }} - {{ x }} + {% when None %} + {{ subst.time.0 }} + {% endmatch %} + {{ subst.classes|join(", ") }}{{ subst.prev_subject }} + {% match subst.subject %} + {% when Some with (x) %} + {{ x }} + {% when None %} + --- + {% endmatch %} + {{ subst.teachers|join(", ") }} + {% match subst.prev_room %} + {% when Some with (x) %} + {{ x }} + {% when None %} + --- + {% endmatch %} + + {% match subst.room %} + {% when Some with (x) %} + {{ x }} + {% when None %} + --- + {% endmatch %} + + {% match subst.text %} + {% when Some with (x) %} + {{ x }} + {% when None %} + {% endmatch %} +
+
+ + + + + + diff --git a/config/nginx/nginx.conf b/config/nginx/nginx.conf index 6b6d3ca..9fb2f8d 100644 --- a/config/nginx/nginx.conf +++ b/config/nginx/nginx.conf @@ -15,8 +15,8 @@ http { server { listen 80; - location /graphql { - proxy_pass http://api:80/graphql; + location / { + proxy_pass http://web; proxy_buffering off; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; @@ -36,5 +36,33 @@ http { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } + + location /redis { + proxy_pass http://redis-commander:8081; + proxy_buffering off; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location ~* /rabbitmq/api/(.*?)/(.*) { + proxy_pass http://rabbitmq:15672/api/$1/%2F/$2?$query_string; + proxy_buffering off; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location ~* /rabbitmq/(.*) { + rewrite ^/rabbitmq/(.*)$ /$1 break; + proxy_pass http://rabbitmq:15672; + proxy_buffering off; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } } } diff --git a/docker-compose.yml b/docker-compose.yml index ed3d6ec..2750bf2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,18 +1,18 @@ version: "3" -x-backend: - &backend +x-bvplan: + &bvplan image: git.dergrimm.net/dergrimm/bvplan_backend:latest build: - context: ./backend + context: ./bvplan restart: always command: worker depends_on: - - postgres + - db - rabbitmq - redis environment: - BACKEND_DB_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_USER} + BACKEND_DB_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_USER} BACKEND_AMQP_URL: amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@rabbitmq:5672 BACKEND_REDIS_URL: redis://redis:6379 BACKEND_UNTIS_API_URL: ${BACKEND_UNTIS_API_URL} @@ -33,28 +33,30 @@ services: - ./config/nginx/nginx.conf:/etc/nginx/nginx.conf:ro ports: - 80:80 + - 8080:8080 depends_on: - adminer - rabbitmq - - api + - web + - redis-commander - postgres: - image: docker.io/postgres:alpine + db: + image: docker.io/postgres:15-alpine restart: always environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - - postgres:/var/lib/postgresql/data + - db:/var/lib/postgresql/data adminer: image: docker.io/adminer:standalone restart: always depends_on: - - postgres + - db rabbitmq: - image: docker.io/rabbitmq:3-alpine + image: docker.io/rabbitmq:3-management-alpine restart: always environment: RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER} @@ -68,20 +70,32 @@ services: volumes: - redis:/data - worker: - <<: *backend - command: worker - - api: - <<: *backend - command: api + redis-commander: + image: rediscommander/redis-commander:latest + restart: always + environment: + REDIS_HOSTS: local:redis:6379 + URL_PREFIX: /redis depends_on: - - postgres + - redis + + worker: + <<: *bvplan + command: worker + volumes: + - ./data/backups:/backups + + web: + <<: *bvplan + command: web + depends_on: + - db - rabbitmq - worker + - redis volumes: - postgres: + db: rabbitmq: redis: