diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 62e90f580..c65a27b4d 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -319,6 +319,11 @@ jobs: unzip -o *.zip rm *.zip ls -l + - name: Copy utils folder + shell: bash + run: | + cp -r utils backend/dist/utils + cp -r utils sync-microservice/dist/utils - name: Setup Node uses: actions/setup-node@v4 with: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5d93a00b6..554a7f032 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,12 @@ repos: - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: "v0.0.241" + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.10 hooks: - id: ruff args: [--fix] - repo: https://github.com/psf/black - rev: "22.3.0" + rev: 24.4.2 hooks: - id: black language_version: python3 diff --git a/COPYRIGHT.md b/COPYRIGHT.md new file mode 100644 index 000000000..483657ea5 --- /dev/null +++ b/COPYRIGHT.md @@ -0,0 +1,8 @@ +Copyright © 2025 AOSSIE
+All rights reserved. + +All works in this repository may be used according to the conditions +stated in the LICENSE.md file available in this repository. + +These works are WITHOUT ANY WARRANTY, without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000..175443ce8 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,595 @@ +GNU General Public License +========================== + +_Version 3, 29 June 2007_ +_Copyright © 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) + + 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) + 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/app.py b/app.py index 65e607cbe..a47ebbc53 100644 --- a/app.py +++ b/app.py @@ -1,6 +1,7 @@ # ...existing code... from utils.cache import invalidate_cache + # Add cache reset option when application starts def initialize_app(): # ...existing code... diff --git a/backend/app/config/settings.py b/backend/app/config/settings.py index 124621a4a..507cba473 100644 --- a/backend/app/config/settings.py +++ b/backend/app/config/settings.py @@ -2,7 +2,7 @@ MODEL_EXPORTS_PATH = "app/models/ONNX_Exports" # Microservice URLs -SYNC_MICROSERVICE_URL = "http://localhost:8001/api/v1" +SYNC_MICROSERVICE_URL = "http://localhost:52124" CONFIDENCE_PERCENT = 0.6 # Object Detection Models: diff --git a/backend/app/logging/setup_logging.py b/backend/app/logging/setup_logging.py index e64424654..0eedecaa9 100644 --- a/backend/app/logging/setup_logging.py +++ b/backend/app/logging/setup_logging.py @@ -243,11 +243,16 @@ def emit(self, record: logging.LogRecord) -> None: # Create a message that includes the original module in the format msg = record.getMessage() - # Find the appropriate logger - logger = get_logger(module_name) - - # Log the message with our custom formatting - logger.log(record.levelno, f"[uvicorn] {msg}") + record.msg = f"[{module_name}] {msg}" + record.args = () + # Clear exception / stack info to avoid duplicate traces + record.exc_info = None + record.stack_info = None + + root_logger = logging.getLogger() + for handler in root_logger.handlers: + if handler is not self: + handler.handle(record) def configure_uvicorn_logging(component_name: str) -> None: diff --git a/backend/app/utils/microservice.py b/backend/app/utils/microservice.py deleted file mode 100644 index 563c00841..000000000 --- a/backend/app/utils/microservice.py +++ /dev/null @@ -1,363 +0,0 @@ -import os -import platform -import subprocess -import sys -from pathlib import Path -from typing import Optional - -import threading -import atexit -from app.logging.setup_logging import get_logger - -logger = get_logger(__name__) - -# Global tracking for subprocess log threads -_log_threads = [] - - -def cleanup_log_threads(): - """Clean up log threads during shutdown to ensure all buffered logs are processed.""" - if _log_threads: - logger.info("Cleaning up log threads...") - for thread in _log_threads: - if thread.is_alive(): - thread.join(timeout=2.0) # Wait up to 2 seconds for each thread - _log_threads.clear() - logger.info("Log threads cleanup completed") - - -# Register cleanup function to run at exit -atexit.register(cleanup_log_threads) - - -def microservice_util_stop_sync_service(): - """ - Stop the sync microservice and clean up log threads. - This function should be called during application shutdown. - """ - cleanup_log_threads() - - -def microservice_util_start_sync_service( - sync_service_path: Optional[str] = None, -) -> bool: - """ - Start the sync microservice with automatic virtual environment management. - - When running as a frozen executable (PyInstaller), it will use the bundled - PictoPy_Sync executable. Otherwise, it uses the development setup with venv. - - Args: - sync_service_path: Path to the sync microservice directory. - If None, defaults to 'sync-microservice' relative to project root. - - Returns: - bool: True if service started successfully, False otherwise. - """ - try: - # Check if running as a frozen executable (PyInstaller) - if getattr(sys, "frozen", False): - logger.info( - "Running as frozen executable, using bundled sync microservice..." - ) - return _start_frozen_sync_service() - - # Development mode - use virtual environment setup - logger.info("Running in development mode, using virtual environment...") - return _start_dev_sync_service(sync_service_path) - - except Exception as e: - logger.error(f"Error starting sync microservice: {e}") - return False - - -CYAN = "\033[96m" -RED = "\033[91m" -MAGENTA = "\033[95m" -RESET = "\033[0m" - - -def stream_logs(pipe, prefix, color): - """Read a process pipe and print formatted logs from sync-microservice.""" - for line in iter(pipe.readline, ""): - if line: - # Trim any trailing newlines - line = line.strip() - if line: - # All output from sync-microservice is now properly formatted by its logger - print(line) - pipe.close() - - -def _start_frozen_sync_service() -> bool: - """ - Start the sync microservice when running as a frozen executable. - The sync microservice executable should be in the PictoPy_Sync folder. - """ - try: - # Get the directory where the current executable is located - if getattr(sys, "frozen", False): - # When frozen, sys.executable points to the main executable - app_dir = Path(sys.executable).parent.parent - else: - # Fallback (shouldn't happen in this function) - app_dir = Path(__file__).parent.parent.parent.parent - - # Look for the sync-microservice directory and executable - sync_dir = app_dir / "sync-microservice" - - # Determine executable name based on platform - system = platform.system().lower() - if system == "windows": - sync_executable = sync_dir / "PictoPy_Sync.exe" - else: - sync_executable = sync_dir / "PictoPy_Sync" - - if not sync_executable.exists(): - logger.error( - f"Sync microservice executable not found at: {sync_executable}" - ) - return False - - logger.info(f"Starting sync microservice from: {sync_executable}") - - # Start the sync microservice executable - cmd = str(sync_executable) # Correct the command to use the actual path - logger.info(f"Starting sync microservice with command: {cmd}") - - process = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - bufsize=1, # Line buffered output - ) - - # Start background threads to forward output to logger - # Stream stdout with consistent SYNC-MICROSERVICE prefix - t1 = threading.Thread( - target=stream_logs, - args=(process.stdout, "SYNC-MICROSERVICE", CYAN), - daemon=False, - ) - - # Stream stderr with consistent SYNC-MICROSERVICE-ERR prefix - t2 = threading.Thread( - target=stream_logs, - args=(process.stderr, "SYNC-MICROSERVICE-ERR", RED), - daemon=False, - ) - - t1.start() - t2.start() - _log_threads.extend([t1, t2]) - - logger.info(f"Sync microservice started with PID: {process.pid}") - logger.info("Service should be available at http://localhost:8001") - - return True - - except Exception as e: - logger.error(f"Error starting frozen sync microservice: {e}") - return False - - -def _start_dev_sync_service(sync_service_path: Optional[str] = None) -> bool: - """ - Start the sync microservice in development mode using virtual environment. - """ - try: - # Determine the sync service path - if sync_service_path is None: - # Get project root (assuming this file is in backend/app/utils/) - current_file = Path(__file__) - project_root = current_file.parent.parent.parent.parent - sync_service_path = project_root / "sync-microservice" - else: - sync_service_path = Path(sync_service_path) - - if not sync_service_path.exists(): - logger.error(f"Sync service directory not found: {sync_service_path}") - return False - - # Define virtual environment path - venv_path = sync_service_path / ".sync-env" - - # Check if virtual environment exists - if not venv_path.exists(): - logger.info("Virtual environment not found. Creating .sync-env...") - if not _create_virtual_environment(venv_path): - logger.error("Failed to create virtual environment") - return False - - # Get the Python executable path from the virtual environment - python_executable = _get_venv_python_executable(venv_path) - if not python_executable: - logger.error("Failed to locate Python executable in virtual environment") - return False - - # Install dependencies if requirements.txt exists - requirements_file = sync_service_path / "requirements.txt" - if requirements_file.exists(): - logger.info("Installing dependencies...") - if not _install_requirements(python_executable, requirements_file): - logger.warning("Failed to install requirements, but continuing...") - - # Start the FastAPI service - logger.info("Starting sync microservice on port 8001...") - return _start_fastapi_service(python_executable, sync_service_path) - - except Exception as e: - logger.error(f"Error starting dev sync microservice: {e}") - return False - - -def _create_virtual_environment(venv_path: Path) -> bool: - """Create a virtual environment at the specified path.""" - try: - # Use the current Python interpreter to create the virtual environment - cmd = [sys.executable, "-m", "venv", str(venv_path)] - - result = subprocess.run( - cmd, - capture_output=True, - text=True, - timeout=300, # 5 minute timeout - ) - - if result.returncode == 0: - logger.info(f"Virtual environment created successfully at {venv_path}") - return True - else: - logger.error(f"Failed to create virtual environment: {result.stderr}") - return False - - except subprocess.TimeoutExpired: - logger.error("Virtual environment creation timed out") - return False - except Exception as e: - logger.error(f"Error creating virtual environment: {e}") - return False - - -def _get_venv_python_executable(venv_path: Path) -> Optional[Path]: - """Get the Python executable path from the virtual environment.""" - system = platform.system().lower() - - if system == "windows": - # Windows: .sync-env/Scripts/python.exe - python_exe = venv_path / "Scripts" / "python.exe" - else: - # Unix/Linux/macOS: .sync-env/bin/python - python_exe = venv_path / "bin" / "python" - - if python_exe.exists(): - return python_exe - else: - logger.error(f"Python executable not found at {python_exe}") - return None - - -def _install_requirements(python_executable: Path, requirements_file: Path) -> bool: - """Install requirements using pip in the virtual environment.""" - try: - cmd = [ - str(python_executable), - "-m", - "pip", - "install", - "-r", - str(requirements_file), - ] - - result = subprocess.run( - cmd, - capture_output=True, - text=True, - timeout=600, # 10 minute timeout for pip install - ) - - if result.returncode == 0: - logger.info("Requirements installed successfully") - return True - else: - logger.error(f"Failed to install requirements: {result.stderr}") - return False - - except subprocess.TimeoutExpired: - logger.error("Requirements installation timed out") - return False - except Exception as e: - logger.error(f"Error installing requirements: {e}") - return False - - -def _start_fastapi_service(python_executable: Path, service_path: Path) -> bool: - """Start the FastAPI service using the virtual environment Python.""" - try: - # Change to the service directory - original_cwd = os.getcwd() - os.chdir(service_path) - - # Command to start FastAPI server - logger.debug(f"Using Python executable: {python_executable}") - host = "127.0.0.1" # Local connections only for security - port = "8001" - - # Basic uvicorn command that works on all platforms - cmd = [ - str(python_executable), - "-m", - "uvicorn", - "main:app", - "--host", - host, - "--port", - port, - "--reload", # Enable auto-reload for development - ] - - logger.info(f"Executing command: {' '.join(cmd)}") - - # Start the process (non-blocking) - process = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - bufsize=1, # Line buffered output - ) - - # Start background threads to forward output to logger - t1 = threading.Thread( - target=stream_logs, - args=(process.stdout, "SYNC-MICROSERVICE", CYAN), - daemon=False, - ) - - t2 = threading.Thread( - target=stream_logs, - args=(process.stderr, "SYNC-MICROSERVICE-ERR", RED), - daemon=False, - ) - - t1.start() - t2.start() - _log_threads.extend([t1, t2]) - - # Restore original working directory - os.chdir(original_cwd) - - logger.info(f"Sync microservice started with PID: {process.pid}") - logger.info("Service should be available at http://localhost:8001") - - return True - - except Exception as e: - logger.error(f"Error starting FastAPI service: {e}") - # Restore original working directory in case of error - try: - os.chdir(original_cwd) - except Exception: - pass - return False diff --git a/backend/main.py b/backend/main.py index 2c1f39e44..db591cd97 100644 --- a/backend/main.py +++ b/backend/main.py @@ -19,7 +19,6 @@ from app.database.albums import db_create_album_images_table from app.database.folders import db_create_folders_table from app.database.metadata import db_create_metadata_table -from app.utils.microservice import microservice_util_start_sync_service from app.routes.folders import router as folders_router from app.routes.albums import router as albums_router @@ -52,7 +51,6 @@ async def lifespan(app: FastAPI): db_create_albums_table() db_create_album_images_table() db_create_metadata_table() - microservice_util_start_sync_service() # Create ProcessPoolExecutor and attach it to app.state app.state.executor = ProcessPoolExecutor(max_workers=1) @@ -72,7 +70,7 @@ async def lifespan(app: FastAPI): "url": "https://www.postman.com/aossie-pictopy/pictopy/overview", }, servers=[ - {"url": "http://localhost:8000", "description": "Local Development server"} + {"url": "http://localhost:52123", "description": "Local Development server"} ], ) @@ -143,8 +141,8 @@ async def root(): # Create a simple config with log_config=None to disable Uvicorn's default logging config = Config( app=app, - host="0.0.0.0", - port=8000, + host="localhost", + port=52123, log_level="info", log_config=None, # This is crucial - disable Uvicorn's default logging config ) diff --git a/docs/Manual_Setup_Guide.md b/docs/Manual_Setup_Guide.md index dc3b5d917..d2512e3be 100644 --- a/docs/Manual_Setup_Guide.md +++ b/docs/Manual_Setup_Guide.md @@ -24,6 +24,24 @@ cd PictoPy git remote add upstream https://github.com/AOSSIE-Org/PictoPy ``` +### Prerequisites: + +#### Install and Setup Miniconda + +Before setting up the Python backend and sync-microservice, you need to have **Miniconda** installed and set up on your system. + +1. **Download and Install Miniconda:** + + - Visit the [Miniconda installation guide](https://www.anaconda.com/docs/getting-started/miniconda/install#quickstart-install-instructions) + - Follow the quickstart install instructions for your operating system + - Make sure `conda` is available in your terminal after installation + +2. **Verify Installation:** + ```bash + conda --version + ``` + You should see the conda version number if installed correctly. + ### Tauri Frontend Setup: 1. **Install Tauri prerequisites based on your OS using this** [guide](https://tauri.app/start/prerequisites/). @@ -43,7 +61,7 @@ git remote add upstream https://github.com/AOSSIE-Org/PictoPy ### Python (FastAPI) Backend Setup Steps: -> **Note:** For backend setup make sure that you have **Python version 3.12**. Additionally, for Windows, make sure that you are using Powershell for the setup, not command prompt. +> **Note:** For backend setup make sure that you have **Miniconda installed** (see Prerequisites section above). Additionally, for Windows, make sure that you are using Powershell for the setup, not command prompt. 1. **Navigate to the Backend Directory:** Open your terminal and use `cd` to change directories: @@ -53,59 +71,91 @@ git remote add upstream https://github.com/AOSSIE-Org/PictoPy cd backend ``` -2. **Set Up a Virtual Environment (Highly Recommended):** Virtual environments isolate project dependencies. Create one using: +2. **Create a Conda Environment:** Create a new conda environment with Python 3.12: - Bash(Linux/MacOS) + Bash/Powershell ``` - python3 -m venv .env + conda create -p .env python=3.12 ``` - Powershell(Windows) +3. **Activate the Conda Environment:** + + Bash/Powershell ``` - python -m venv .env + conda activate ./.env ``` -3. **Activate the Virtual Environment:** +4. **Install Dependencies:** The `requirements.txt` file lists required packages. Install them using pip: - Bash(Linux/MacOS) + Bash/Powershell ``` - source .env/bin/activate + pip install -r requirements.txt ``` - Powershell(Windows) +5. **Running the backend:**: To start the backend in development mode, run this command while being in the backend folder and the conda environment activated: + + Bash/Powershell ``` - .env\Scripts\activate.ps1 + fastapi dev --port 52123 ``` - After activating, you should be able to see the virtual environment's name before the current path. Something like this: + The server will start on `http://localhost:52123` by default. In test mode, the server will automatically restart if any errors are detected or if source files are modified. - ![alt text](/docs/assets/screenshots/virtualEnv.png) + ![alt text](/docs/assets/screenshots/serverRunning.png) -4. **Install Dependencies:** The `requirements.txt` file lists required packages. Install them using pip: +### Sync-Microservice Setup Steps: + +> **Note:** For sync-microservice setup make sure that you have **Miniconda installed** (see Prerequisites section above). Additionally, for Windows, make sure that you are using Powershell for the setup, not command prompt. + +1. **Navigate to the Sync-Microservice Directory:** Open your terminal and use `cd` to change directories: Bash + ``` + cd sync-microservice + ``` + +2. **Create a Conda Environment:** Create a new conda environment with Python 3.12: + + Bash/Powershell + + ``` + conda create -p .sync-env python=3.12 + ``` + +3. **Activate the Conda Environment:** + + Bash/Powershell + + ``` + conda activate ./.sync-env + ``` + +4. **Install Dependencies:** The `requirements.txt` file lists required packages. Install them using pip: + + Bash/Powershell + ``` pip install -r requirements.txt ``` -5. **Running the backend:**: To start the backend in development mode, run this command while being in the backend folder and the virtual environment activated: +5. **Running the sync-microservice:** To start the sync-microservice in development mode, run this command while being in the sync-microservice folder and the conda environment activated: Bash/Powershell ``` - fastapi dev + fastapi dev --port 52124 ``` - The server will start on `http://localhost:8000` by default. In test mode, the server will automatically restart if any errors are detected or if source files are modified. + The server will start on `http://localhost:52124` by default. In development mode, the server will automatically restart if any errors are detected or if source files are modified. - ![alt text](/docs/assets/screenshots/serverRunning.png) +### Troubleshooting Common Issues: -6. **Missing System Dependencies:** Some dependencies might need system-level libraries like `libGL.so.1` (often needed by OpenCV). Install the appropriate packages based on your distribution: +1. **Missing System Dependencies:** Some dependencies might need system-level libraries like `libGL.so.1` (often needed by OpenCV). Install the appropriate packages based on your distribution: **Debian/Ubuntu:** @@ -119,7 +169,7 @@ git remote add upstream https://github.com/AOSSIE-Org/PictoPy **Other Systems:** Consult your distribution's documentation for installation instructions. -7. **`gobject-2.0` Not Found Error:** Resolve this error by installing `libglib2.0-dev` (Debian/Ubuntu): +2. **`gobject-2.0` Not Found Error:** Resolve this error by installing `libglib2.0-dev` (Debian/Ubuntu): Bash diff --git a/docs/Script_Setup_Guide.md b/docs/Script_Setup_Guide.md index f2e08f73f..6ef284aef 100644 --- a/docs/Script_Setup_Guide.md +++ b/docs/Script_Setup_Guide.md @@ -5,6 +5,8 @@ - [Windows](https://youtu.be/nNVAE4or280?si=j_y9Xn8Kra6tPHjw) - [Ubuntu (Debian)](https://www.youtube.com/watch?v=a7I0ZRE-SHk) +> Note that the Step No. 8 given below is not mentioned in the video, please follow that step also. + ### Prerequisites: - [NodeJS](https://nodejs.org/en) (LTS Version Recommended) @@ -48,8 +50,8 @@ ```powershell cd .\backend - .env\Scripts\activate.ps1 - fastapi dev + .env\Scripts\Activate.ps1 + fastapi dev --port 52123 ``` #### Linux @@ -57,10 +59,30 @@ ```bash cd ./backend source .env/bin/activate - fastapi dev + fastapi dev --port 52123 + ``` + +8. Start the Sync-Microservice + + Open a new terminal window, navigate to the project directory, and run: + + #### Windows + + ```powershell + cd .\sync-microservice + .sync-env\Scripts\Activate.ps1 + fastapi dev --port 52124 + ``` + + #### Linux + + ```bash + cd ./sync-microservice + source .sync-env/bin/activate + fastapi dev --port 52124 ``` -8. Start the Frontend Desktop App +9. Start the Frontend Desktop App Open a new terminal window, navigate to the project directory, and run: @@ -69,12 +91,12 @@ npm run tauri dev ``` -9. Pre-commit Setup +10. Pre-commit Setup - Before running the `git commit` command, ensure you have the following Python packages installed globally: + Before running the `git commit` command, ensure you have the following Python packages installed globally: - ```bash - pip install ruff black mypy pre-commit - ``` + ```bash + pip install ruff black mypy pre-commit + ``` - > **Note:** If you are committing from a virtual environment, these packages should already be installed as they are included in the requirements.txt file. + > **Note:** If you are committing from a virtual environment, these packages should already be installed as they are included in the requirements.txt file. diff --git a/docs/backend/backend_python/openapi.json b/docs/backend/backend_python/openapi.json index a29e7c4f1..c8b50c88c 100644 --- a/docs/backend/backend_python/openapi.json +++ b/docs/backend/backend_python/openapi.json @@ -11,7 +11,7 @@ }, "servers": [ { - "url": "http://localhost:8000", + "url": "http://localhost:52123", "description": "Local Development server" } ], @@ -1117,14 +1117,9 @@ "in": "query", "required": false, "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/InputType" - } - ], + "$ref": "#/components/schemas/InputType", "description": "Choose input type: 'path' or 'base64'", - "default": "path", - "title": "Input Type" + "default": "path" }, "description": "Choose input type: 'path' or 'base64'" } @@ -2204,6 +2199,7 @@ "metadata": { "anyOf": [ { + "additionalProperties": true, "type": "object" }, { diff --git a/docs/backend/backend_rust/api.md b/docs/backend/backend_rust/api.md index 02b4d5aff..d2d4b25fb 100644 --- a/docs/backend/backend_rust/api.md +++ b/docs/backend/backend_rust/api.md @@ -2,10 +2,10 @@ The Rust backend provides the following command that can be invoked from the frontend: -### get_server_path +### get_resources_folder_path - **Description**: Retrieves the path to the server resources directory. -- **Parameters**: *None* +- **Parameters**: _None_ - **Returns**: `Result` ## Usage Examples @@ -15,12 +15,12 @@ The Rust backend provides the following command that can be invoked from the fro import { invoke } from "@tauri-apps/api/tauri"; // Example: Get server path -const serverPath = await invoke("get_server_path"); -console.log("Server path:", serverPath); +const resourcesFolderPath = await invoke("get_resources_folder_path"); +console.log("Resources folder path:", serverPath); ``` ## Cross-Platform Support The API provides cross-platform support using Tauri's unified `AppHandle.path().resolve(..., BaseDirectory::Resource)` for path resolution across Windows, macOS, and Linux. -This backend provides essential path resolution functionality for the Tauri application. \ No newline at end of file +This backend provides essential path resolution functionality for the Tauri application. diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index e7f412d2b..d26b9c2f0 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -22,6 +22,24 @@ /* Responsive tweaks: ensure header, images, tables and code blocks behave well on small screens */ +/* Global text wrapping to prevent long words/URLs from overflowing */ +.md-typeset, +.md-content { + overflow-wrap: anywhere; + word-break: break-word; +} + +/* Headings should wrap on small screens instead of overflowing */ +.md-typeset h1, +.md-typeset h2, +.md-typeset h3, +.md-typeset h4, +.md-typeset h5, +.md-typeset h6 { + overflow-wrap: anywhere; + word-break: break-word; +} + /* Make images scale down to container */ .md-typeset img, .md-content img { @@ -43,6 +61,12 @@ overflow-x: auto; } +/* Inline code should also wrap to avoid pushing layout */ +.md-typeset code:not(pre code) { + white-space: break-spaces; + word-break: break-word; +} + /* General container/content spacing adjustments for smaller screens */ .md-main, .md-content { padding-left: 1rem; @@ -312,7 +336,6 @@ } } -\ .md-footer-meta__inner.md-grid { display: flex !important; flex-direction: column !important; diff --git a/frontend/src-tauri/capabilities/migrated.json b/frontend/src-tauri/capabilities/migrated.json index 451192eb2..cd84f5574 100644 --- a/frontend/src-tauri/capabilities/migrated.json +++ b/frontend/src-tauri/capabilities/migrated.json @@ -92,7 +92,7 @@ "identifier": "shell:allow-spawn", "allow": [ { - "name": "StartServerUnix", + "name": "StartBackendUnix", "cmd": "bash", "args": ["-c", "./PictoPy_Server"], "sidecar": false @@ -103,7 +103,7 @@ "identifier": "shell:allow-spawn", "allow": [ { - "name": "StartServerWindows", + "name": "StartBackendWindows", "cmd": "powershell", "args": ["./PictoPy_Server"], "sidecar": false @@ -111,23 +111,23 @@ ] }, { - "identifier": "shell:allow-execute", + "identifier": "shell:allow-spawn", "allow": [ { - "name": "killProcessUnix", + "name": "StartSyncServiceUnix", "cmd": "bash", - "args": ["-c", "pkill -f PictoPy_Server"], + "args": ["-c", "./PictoPy_Sync"], "sidecar": false } ] }, { - "identifier": "shell:allow-execute", + "identifier": "shell:allow-spawn", "allow": [ { - "name": "killProcessWindows", + "name": "StartSyncServiceWindows", "cmd": "powershell", - "args": ["taskkill /t /f /im PictoPy_Server.exe"], + "args": ["./PictoPy_Sync"], "sidecar": false } ] diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index ba577d070..316c1c03e 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -22,7 +22,9 @@ fn main() { println!("Resource path: {:?}", resource_path); Ok(()) }) - .invoke_handler(tauri::generate_handler![services::get_server_path,]) + .invoke_handler(tauri::generate_handler![ + services::get_resources_folder_path, + ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/frontend/src-tauri/src/services/mod.rs b/frontend/src-tauri/src/services/mod.rs index a0945e810..d3f94b7f6 100644 --- a/frontend/src-tauri/src/services/mod.rs +++ b/frontend/src-tauri/src/services/mod.rs @@ -2,10 +2,10 @@ use tauri::path::BaseDirectory; use tauri::Manager; #[tauri::command] -pub fn get_server_path(handle: tauri::AppHandle) -> Result { +pub fn get_resources_folder_path(handle: tauri::AppHandle) -> Result { let resource_path = handle .path() - .resolve("resources/backend", BaseDirectory::Resource) + .resolve("resources", BaseDirectory::Resource) .map_err(|e| e.to_string())?; Ok(resource_path.to_string_lossy().to_string()) } diff --git a/frontend/src-tauri/tauri.conf.json b/frontend/src-tauri/tauri.conf.json index c32e7e5d3..8ad815dfd 100644 --- a/frontend/src-tauri/tauri.conf.json +++ b/frontend/src-tauri/tauri.conf.json @@ -62,7 +62,7 @@ "scope": ["**"], "enable": true }, - "csp": "default-src 'self'; img-src 'self' data: asset: http://asset.localhost; media-src 'self' blob: data:; connect-src 'self' ipc: http://ipc.localhost http://localhost:8000 ws://localhost:8000 http://localhost:8001 ws://localhost:8001" + "csp": "default-src 'self'; img-src 'self' data: asset: http://asset.localhost; media-src 'self' blob: data:; connect-src 'self' ipc: http://ipc.localhost http://localhost:52123 ws://localhost:52123 http://localhost:52124 ws://localhost:52124" } } } diff --git a/frontend/src/components/Media/ChronologicalGallery.tsx b/frontend/src/components/Media/ChronologicalGallery.tsx index f033e35a0..eeb38b43f 100644 --- a/frontend/src/components/Media/ChronologicalGallery.tsx +++ b/frontend/src/components/Media/ChronologicalGallery.tsx @@ -95,8 +95,9 @@ export const ChronologicalGallery = ({ }, [onMonthOffsetsChange, scrollContainerRef]); useEffect(() => { + if (images.length === 0) return; recomputeMarkers(); - }, [images, recomputeMarkers]); + }, [images]); useEffect(() => { const elementToObserve = scrollContainerRef?.current ?? galleryRef.current; diff --git a/frontend/src/components/Navigation/Navbar/Navbar.tsx b/frontend/src/components/Navigation/Navbar/Navbar.tsx index c565b7f6d..b6d3d82b9 100644 --- a/frontend/src/components/Navigation/Navbar/Navbar.tsx +++ b/frontend/src/components/Navigation/Navbar/Navbar.tsx @@ -1,96 +1,368 @@ +import { useEffect, useState } from 'react'; import { Input } from '@/components/ui/input'; import { ThemeSelector } from '@/components/ThemeToggle'; -import { Search } from 'lucide-react'; +import { Bell, Search } from 'lucide-react'; +import { Button } from '@/components/ui/button'; import { useDispatch, useSelector } from 'react-redux'; import { selectAvatar, selectName } from '@/features/onboardingSelectors'; import { clearSearch } from '@/features/searchSlice'; import { convertFileSrc } from '@tauri-apps/api/core'; import { FaceSearchDialog } from '@/components/Dialog/FaceSearchDialog'; +import { RootState } from '@/app/store'; +import { usePictoQuery } from '@/hooks/useQueryExtension'; +import { fetchAllClusters } from '@/api/api-functions'; +import { setClusters } from '@/features/faceClustersSlice'; +import { useNavigate } from 'react-router'; +import { Cluster } from '@/types/Media'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; export function Navbar() { + const navigate = useNavigate(); + const dispatch = useDispatch(); + const [data, setData] = useState(''); const userName = useSelector(selectName); const userAvatar = useSelector(selectAvatar); - const searchState = useSelector((state: any) => state.search); const isSearchActive = searchState.active; const queryImage = searchState.queryImage; + const [isFocused, setIsFocused] = useState(false); + const { clusters } = useSelector((state: RootState) => state.faceClusters); - const dispatch = useDispatch(); - return ( -
- {/* Logo */} - + const { data: clustersData, isSuccess: clustersSuccess } = usePictoQuery({ + queryKey: ['clusters'], + queryFn: fetchAllClusters, + }); - {/* Search Bar */} -
-
- {/* Query Image */} - {queryImage && ( -
- Query - {isSearchActive && ( - - )} -
- )} + useEffect(() => { + if (clustersSuccess && clustersData?.data?.clusters) { + const clusters = (clustersData.data.clusters || []) as Cluster[]; + dispatch(setClusters(clusters)); + } + }, [clustersData, clustersSuccess, dispatch]); - {/* Input */} - + const handlePersonClick = (clusterId: string) => { + navigate(`/person/${clusterId}`); + setIsFocused(false); + }; - {/* FaceSearch Dialog */} + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') setIsFocused(false); + if (e.key === 'Enter' && isFocused && data.trim()) { + handleSearch(); + } + }; + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [data, isFocused]); - + const getDateSuggestions = (input: string): string[] => { + const clean = input.toLowerCase().trim(); + if (!clean) return []; - + const months = [ + 'january', + 'february', + 'march', + 'april', + 'may', + 'june', + 'july', + 'august', + 'september', + 'october', + 'november', + 'december', + ]; + + const currentYear = new Date().getFullYear(); + const years = [currentYear, currentYear - 1, currentYear - 2]; + const suggestions: string[] = []; + + // 1. Pehle face names check karo + const matchedFaceNames = faceNames.filter((n) => { + if (!n) return false; + return n.toLowerCase().includes(clean); + }); + + // 2. Phir months check karo + const matchedMonths = months.filter((month) => month.startsWith(clean)); + + // Face names ko suggestions mein add karo + matchedFaceNames.forEach((name) => { + suggestions.push(name); + }); + + // Date suggestions add karo + matchedMonths.forEach((month) => { + years.forEach((year) => { + suggestions.push(`${month} ${year}`); + }); + }); + return suggestions.slice(0, 12); + }; + + const handleSuggestionClick = (suggestion: string) => { + if (!suggestion) return; + setData(suggestion); + navigate(`/search/${encodeURIComponent(suggestion)}`); + setIsFocused(false); + }; + + // Search button handler + const handleSearch = () => { + if (!data.trim()) return; + console.log(`/search/${encodeURIComponent(data)}`); + navigate(`/search/${encodeURIComponent(data)}`); + setIsFocused(false); + }; + + // facename handling + const [faceNames, setFaceNames] = useState([]); + const dateSuggestions = getDateSuggestions(data); // Isme ab face names bhi honge + const hasPartialMatch = dateSuggestions.length > 0; + + useEffect(() => { + if (clusters && clusters.length > 0) { + const names = clusters.map((cluster: any) => cluster.cluster_name); + setFaceNames(names); + // console.log('Face Names:', names); + } + }, [clusters]); + return ( + <> +
+ {/* Logo */} + -
- {/* Right Side */} - -
+ + {/* info about the search bar if user not clicks in the face i will show the date suggestion */} + {isFocused && data === '' && ( + <> +
setIsFocused(false)} + /> +
e.stopPropagation()} + > +
+

+ Search events +

+
+
+ {[ + 'Beach trip', + 'Marriage', + 'Office', + 'Birthday', + 'Graduation', + ].map((item) => ( + handleSuggestionClick(item)} + > + {item} + + ))} +
+

+ Faces +

+ {clusters.length === 0 ? ( + <> +

+ No faces found. PictoPy will automatically detect and group + faces as you add more photos. +

+ + ) : ( + <> +
+ {clusters.map((cluster: any) => ( +
handlePersonClick(cluster.cluster_id)} + > + + + + {cluster.cluster_name?.charAt(0).toUpperCase() || + cluster.cluster_id.charAt(0).toUpperCase()} + + +
+

+ {cluster.cluster_name || + `Person ${cluster.cluster_id.slice(-4)}`} +

+

+ {cluster.face_count} photo + {cluster.face_count !== 1 ? 's' : ''} +

+
+
+ ))} +
+ + )} +
+ + )} + + {isFocused && data !== '' && ( + <> +
setIsFocused(false)} + /> +
e.stopPropagation()} + > + {hasPartialMatch ? ( + <> +
+

+ Search by date , name and more... +

+
+ {dateSuggestions.map((suggestion) => ( +
handleSuggestionClick(suggestion)} + > +

+ {suggestion} +

+
+ ))} +
+
+ + ) : ( + <> +
+
+

+ No matching dates found +

+

+ Try searching for a month name (e.g., "January 2025") +

+
+
+
+
+

+ Search for "{data}" +

+

+ Search in all photos +

+
+ +
+
+
+ + )} +
+ + )} + ); } diff --git a/frontend/src/components/OnboardingSteps/ServerCheck.tsx b/frontend/src/components/OnboardingSteps/ServerCheck.tsx index 94962caa6..7d2448456 100644 --- a/frontend/src/components/OnboardingSteps/ServerCheck.tsx +++ b/frontend/src/components/OnboardingSteps/ServerCheck.tsx @@ -22,7 +22,7 @@ export const ServerCheck: React.FC = ({ stepIndex }) => { } = usePictoQuery({ queryKey: ['clusters'], queryFn: getMainBackendHealthStatus, - retry: 10, + retry: 60, retryDelay: 1000, }); const { @@ -32,7 +32,7 @@ export const ServerCheck: React.FC = ({ stepIndex }) => { } = usePictoQuery({ queryKey: ['syncMicroservice'], queryFn: getSyncMicroserviceHealthStatus, - retry: 10, + retry: 60, retryDelay: 1000, }); useEffect(() => { diff --git a/frontend/src/config/Backend.ts b/frontend/src/config/Backend.ts index 28b16c48f..2013d2d15 100644 --- a/frontend/src/config/Backend.ts +++ b/frontend/src/config/Backend.ts @@ -1,2 +1,2 @@ -export const BACKEND_URL = 'http://localhost:8000'; -export const SYNC_MICROSERVICE_URL = 'http://localhost:8001/api/v1'; +export const BACKEND_URL = 'http://localhost:52123'; +export const SYNC_MICROSERVICE_URL = 'http://localhost:52124'; diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts index 7a8da5bb5..4662902a3 100644 --- a/frontend/src/constants/routes.ts +++ b/frontend/src/constants/routes.ts @@ -9,4 +9,5 @@ export const ROUTES = { ALBUMS: 'albums', MEMORIES: 'memories', PERSON: 'person/:clusterId', + SEARCH: 'search/:query', }; diff --git a/frontend/src/features/searchSlice.ts b/frontend/src/features/searchSlice.ts index 9786277c1..7e8372b07 100644 --- a/frontend/src/features/searchSlice.ts +++ b/frontend/src/features/searchSlice.ts @@ -1,12 +1,15 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { Image } from '@tauri-apps/api/image'; interface SearchState { active: boolean; + images: Image[]; queryImage?: string; } const initialState: SearchState = { active: false, + images: [], queryImage: undefined, }; diff --git a/frontend/src/hooks/useFolderOperations.tsx b/frontend/src/hooks/useFolderOperations.tsx index 0c0fcc559..ff747ff0b 100644 --- a/frontend/src/hooks/useFolderOperations.tsx +++ b/frontend/src/hooks/useFolderOperations.tsx @@ -101,8 +101,7 @@ export const useFolderOperations = () => { useMutationFeedback(enableAITaggingMutation, { showLoading: true, loadingMessage: 'Enabling AI tagging', - successTitle: 'AI Tagging Enabled', - successMessage: 'AI tagging has been enabled for the selected folder.', + showSuccess: false, errorTitle: 'AI Tagging Error', errorMessage: 'Failed to enable AI tagging. Please try again.', }); diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index c76c26065..e01868d7c 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -3,7 +3,7 @@ import ReactDOM from 'react-dom/client'; import App from './App'; import BrowserWarning from './components/BrowserWarning'; import { isProd } from './utils/isProd'; -import { stopServer, startServer } from './utils/serverUtils'; +import { startServer } from './utils/serverUtils'; import { isTauriEnvironment } from './utils/tauriUtils'; import { store } from './app/store'; import { Provider } from 'react-redux'; @@ -18,7 +18,7 @@ const onCloseListener = async () => { try { const { getCurrentWindow } = await import('@tauri-apps/api/window'); await getCurrentWindow().onCloseRequested(async () => { - await stopServer(); + // code to stop the server }); } catch (error) { console.error('Error setting up close listener:', error); diff --git a/frontend/src/pages/PersonImages/PersonImages.tsx b/frontend/src/pages/PersonImages/PersonImages.tsx index ea646b8ad..9a4ddce08 100644 --- a/frontend/src/pages/PersonImages/PersonImages.tsx +++ b/frontend/src/pages/PersonImages/PersonImages.tsx @@ -44,6 +44,7 @@ export const PersonImages = () => { setClusterName(res?.cluster_name || 'random_name'); dispatch(hideLoader()); } + console.log(images); }, [data, isSuccess, isError, isLoading, dispatch]); const handleEditName = () => { diff --git a/frontend/src/pages/SearchImages/SearchImages.tsx b/frontend/src/pages/SearchImages/SearchImages.tsx new file mode 100644 index 000000000..402fd14e4 --- /dev/null +++ b/frontend/src/pages/SearchImages/SearchImages.tsx @@ -0,0 +1,192 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { Image } from '@/types/Media'; +import { setImages } from '@/features/imageSlice'; +import { showLoader, hideLoader } from '@/features/loaderSlice'; +import { selectImages } from '@/features/imageSelectors'; +import { usePictoQuery } from '@/hooks/useQueryExtension'; +import { fetchAllClusters, fetchAllImages } from '@/api/api-functions'; +import { RootState } from '@/app/store'; +import { showInfoDialog } from '@/features/infoDialogSlice'; +import { useNavigate, useParams } from 'react-router'; +import { setClusters } from '@/features/faceClustersSlice'; +import { Cluster } from '@/types/Media'; +import { Button } from '@/components/ui/button'; +import { ArrowLeft } from 'lucide-react'; +import { ROUTES } from '@/constants/routes'; +import { + ChronologicalGallery, + MonthMarker, +} from '@/components/Media/ChronologicalGallery'; +import { EmptyGalleryState } from '@/components/EmptyStates/EmptyGalleryState'; +import TimelineScrollbar from '@/components/Timeline/TimelineScrollbar'; + +export const SearchImages = () => { + const dispatch = useDispatch(); + const navigate = useNavigate(); + const images = useSelector(selectImages); + const searchState = useSelector((state: RootState) => state.search); + const { clusters } = useSelector((state: RootState) => state.faceClusters); + const query = useParams().query || ''; + + const { data: clustersData, isSuccess: clustersSuccess } = usePictoQuery({ + queryKey: ['clusters'], + queryFn: fetchAllClusters, + }); + + // Check if query is a face name and redirect + useEffect(() => { + if (clusters && clusters.length > 0 && query) { + // Find cluster with exact name match + const matchedCluster = clusters.find( + (cluster: Cluster) => + cluster.cluster_name?.toLowerCase().trim() === + query.toLowerCase().trim(), + ); + + if (matchedCluster) { + // Query is a face name, redirect to person page + navigate(`/person/${matchedCluster.cluster_id}`); + return; + } + } + }, [clusters, query, navigate]); + + useEffect(() => { + if (clustersSuccess && clustersData?.data?.clusters) { + const clusters = (clustersData.data.clusters || []) as Cluster[]; + dispatch(setClusters(clusters)); + } + }, [clustersData, clustersSuccess, dispatch]); + + const isSearchActive = searchState.active; + const searchResults = searchState.images; + + const handleMonthOffsetsChange = useCallback((entries: MonthMarker[]) => { + setMonthMarkers((prev) => { + if ( + prev.length === entries.length && + prev.every( + (m, i) => + m.offset === entries[i].offset && + m.month === entries[i].month && + m.year === entries[i].year, + ) + ) { + return prev; + } + return entries; + }); + }, []); + + const fetchAllImagesWrapper = async ({ + queryKey, + }: { + queryKey: [string, boolean?]; + }) => { + const [, tagged] = queryKey; + return fetchAllImages(tagged); + }; + const [monthMarkers, setMonthMarkers] = useState([]); + const { data, isLoading, isSuccess, isError } = usePictoQuery({ + queryKey: ['images'], + queryFn: fetchAllImagesWrapper, + enabled: !isSearchActive, // Fixed typo here + }); + + useEffect(() => { + if (!isSearchActive) { + if (isLoading) { + dispatch(showLoader('Loading images')); + } else if (isError) { + dispatch(hideLoader()); + dispatch( + showInfoDialog({ + title: 'Error', + message: 'Failed to load images. Please try again later.', + variant: 'error', + }), + ); + } else if (isSuccess) { + const images = data?.data as Image[]; + dispatch(setImages(images)); + dispatch(hideLoader()); + } + } + }, [data, isSuccess, isError, isLoading, dispatch, isSearchActive]); + + // Filter by month + const filterImagesByMonthYear = ( + images: Image[], + monthYearString: string | null, + ) => { + if (!monthYearString) return images; + return images.filter((img) => { + if (!img.metadata?.date_created) return false; + const dateCreated = new Date(img.metadata.date_created); + const imgMonthYear = dateCreated.toLocaleDateString('en-US', { + month: 'long', + year: 'numeric', + }); + return imgMonthYear === monthYearString; + }); + }; + const scrollableRef = useRef(null); + + // Format query + const selectedMonthYear = query + ? query + .split(' ') + .map( + (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(), + ) + .join(' ') + : null; + const displayImages = filterImagesByMonthYear(images, selectedMonthYear); + const title = selectedMonthYear + ? `${selectedMonthYear} (${displayImages.length} images)` + : isSearchActive && searchResults.length > 0 + ? `Face Search Results (${searchResults.length} found)` + : 'All Images'; + + return ( +
+ {/* Gallery Section */} +
+ + {displayImages.length > 0 ? ( + + ) : ( + + )} +
+ + {/* Timeline Scrollbar */} + {monthMarkers.length > 0 && ( + + )} +
+ ); +}; diff --git a/frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx b/frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx index 69c9abaa0..bcecd3f08 100644 --- a/frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx +++ b/frontend/src/pages/SettingsPage/components/ApplicationControlsCard.tsx @@ -1,16 +1,10 @@ import React, { useState } from 'react'; -import { - Settings as SettingsIcon, - RefreshCw, - Server, - Users, -} from 'lucide-react'; +import { Settings as SettingsIcon, RefreshCw, Users } from 'lucide-react'; import { Button } from '@/components/ui/button'; import UpdateDialog from '@/components/Updater/UpdateDialog'; import SettingsCard from './SettingsCard'; -import { restartServer } from '@/utils/serverUtils'; import { useUpdater } from '@/hooks/useUpdater'; import { useDispatch } from 'react-redux'; import { showLoader, hideLoader } from '@/features/loaderSlice'; @@ -140,17 +134,6 @@ const ApplicationControlsCard: React.FC = () => {
- -