├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── app ├── ccip.py ├── config-sample.py ├── delivery-permission-sample.json ├── error.py ├── import.py ├── models │ ├── __init__.py │ ├── announcement.py │ ├── attendee.py │ └── puzzle.py ├── puzzle-config-sample.json ├── reg-sample.csv ├── scenario-sample.json ├── staff-sample.csv └── webhook.sh ├── docker-compose.yml ├── docker-entrypoint.sh ├── nginx.conf ├── poetry.lock ├── pyproject.toml └── uwsgi.ini /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Editor tempopary files 7 | *.sw[op] 8 | *~ 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | env/ 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *,cover 50 | .hypothesis/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # IPython Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | venv/ 87 | ENV/ 88 | 89 | # Spyder project settings 90 | .spyderproject 91 | 92 | # Rope project settings 93 | .ropeproject 94 | 95 | # Config file 96 | config.py 97 | scenario*.json 98 | puzzle-config.json 99 | delivery-permission.json 100 | 101 | reg.csv 102 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12 2 | 3 | COPY ./pyproject.toml / 4 | 5 | RUN curl -sSL https://install.python-poetry.org | python3 - && /root/.local/bin/poetry install 6 | 7 | COPY ./app /app 8 | COPY ./docker-entrypoint.sh /app/docker-entrypoint.sh 9 | 10 | EXPOSE 5000 11 | WORKDIR /app 12 | ENTRYPOINT ["bash", "docker-entrypoint.sh"] 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 2020 OPass 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published by 637 | the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | DOCKER_DEFAULT_PLATFORM ?= linux/amd64 4 | 5 | .EXPORT_ALL_VARIABLES: 6 | 7 | .ONESHELL: 8 | 9 | .phony: 10 | help 11 | 12 | ## ============================================================================ 13 | ## Help Commands 14 | 15 | help: ## Show help 16 | sed -ne '/sed/!s/## //p' $(MAKEFILE_LIST) 17 | 18 | ## ============================================================================ 19 | ## docker Commands 20 | 21 | go: ## All-in-one run container 22 | make rm \ 23 | && make build \ 24 | && make up \ 25 | && make log 26 | 27 | build: ## build container image via docker 28 | $(call FUNC_MAKE_INIT) \ 29 | && docker build \ 30 | --file Dockerfile \ 31 | --tag ccip-app/ccip-server:latest \ 32 | . 33 | 34 | up: ## run container 35 | $(call FUNC_MAKE_INIT) \ 36 | && docker compose \ 37 | up \ 38 | --detach 39 | 40 | log: ## get container log 41 | $(call FUNC_MAKE_INIT) \ 42 | && docker compose \ 43 | logs \ 44 | --follow \ 45 | --tail 20 46 | 47 | rm: ## rm container 48 | $(call FUNC_MAKE_INIT) \ 49 | && docker compose \ 50 | rm \ 51 | --stop \ 52 | --force 53 | 54 | shell: ## bash in container 55 | $(call FUNC_MAKE_INIT) \ 56 | && docker compose \ 57 | exec \ 58 | backend \ 59 | bash 60 | 61 | define FUNC_MAKE_INIT 62 | if [ -n "$$(command -v hr)" ]; then hr -; else echo "-----";fi \ 63 | && echo "⚙️ Running Makefile target: ${MAKECMDGOALS}" 64 | endef 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CCIP-Server 2 | 3 | A Community Checkin with Interactivity Project Server 4 | 5 | ## Pre-Requirement & run 6 | 7 | Read `Dockerfile` 8 | 9 | ## Run CCIP-Server with Docker 10 | 11 | - Make sure `docker` and `make` are installed. 12 | - Execute the follwoing commands every time you change the `config`, `json` or `csv` files. 13 | 14 | ```bash 15 | make go 16 | ``` 17 | 18 | You should get the following messages if it works. 19 | 20 | ``` 21 | ccip_server | INFO:waitress:Serving on http://0.0.0.0:5000 22 | ``` 23 | 24 | ## Get into the running CCIP-Server container instance 25 | 26 | ```bash 27 | make shell 28 | ``` 29 | -------------------------------------------------------------------------------- /app/ccip.py: -------------------------------------------------------------------------------- 1 | import time 2 | import json 3 | 4 | from datetime import datetime 5 | from error import Error 6 | from flask import Flask, Response, request, jsonify 7 | from mongoengine.queryset import DoesNotExist 8 | from functools import wraps 9 | from models import Attendee, Announcement, PuzzleStatus, PuzzleBucket 10 | from random import randint 11 | 12 | import config 13 | 14 | app = Flask(__name__) 15 | app.config.from_pyfile('config.py') 16 | 17 | scenarios_def = {} 18 | for role, filename in config.SCENARIO_DEFS.items(): 19 | with open(filename) as json_file: 20 | scenarios_def[role] = json.load(json_file) 21 | 22 | 23 | try: 24 | with open('puzzle-config.json', 'r') as puzzle_config_json: 25 | puzzle_config = json.load(puzzle_config_json) 26 | except IOError: 27 | puzzle_config = None 28 | app.logger.info('puzzle-config.json not found, not enable puzzle') 29 | 30 | try: 31 | with open('delivery-permission.json', 'r') as delivery_permission_json: 32 | delivery_permission = json.load(delivery_permission_json) 33 | except IOError: 34 | delivery_permission = None 35 | app.logger.info('delivery-permission.json not found, can not deliver puzzle') 36 | 37 | if puzzle_config is not None: 38 | puzzle_status_init = True if (PuzzleStatus.objects.count() == 0) else False 39 | 40 | base = 0 41 | for k, v in puzzle_config.items(): 42 | base += v 43 | 44 | puzzle_rate = {} 45 | for k, v in puzzle_config.items(): 46 | puzzle_rate[k] = v / base 47 | 48 | if puzzle_status_init: 49 | PuzzleStatus(puzzle=k).save() 50 | 51 | if puzzle_status_init: 52 | PuzzleStatus(puzzle='total').save() 53 | 54 | 55 | def deliver_puzzle(attendee, deliverer=None): 56 | try: 57 | puzzle_bucket = PuzzleBucket.objects(public_token=attendee.public_token).get() 58 | except DoesNotExist: 59 | puzzle_bucket = PuzzleBucket.init(attendee) 60 | 61 | if deliverer is not None: 62 | if deliverer in list(map(lambda d: d['deliverer'], puzzle_bucket.deliverer)): 63 | raise Error('Already take from this deliverer') 64 | else: 65 | puzzle_bucket.deliverer.append({ 66 | "deliverer": deliverer, 67 | "timestamp": time.time() 68 | }) 69 | 70 | total = PuzzleStatus.objects(puzzle='total').get().quantity 71 | 72 | for i in range(len(puzzle_config)): 73 | puzzle = list(puzzle_config.keys())[randint(0, len(puzzle_config) - 1)] 74 | if i == len(puzzle_config) - 1 or total == 0 or PuzzleStatus.objects(puzzle=puzzle).get().currency / total < puzzle_rate[puzzle]: 75 | puzzle_bucket.puzzle.append(puzzle) 76 | PuzzleStatus.objects(puzzle='total').update_one(inc__quantity=1, inc__currency=1) 77 | PuzzleStatus.objects(puzzle=puzzle).update_one(inc__quantity=1, inc__currency=1) 78 | break 79 | 80 | puzzle_bucket.save() 81 | 82 | 83 | def returns_json(f): 84 | @wraps(f) 85 | def decorated_function(*args, **kwargs): 86 | r = f(*args, **kwargs) 87 | return Response(r, content_type='application/json; charset=utf-8') 88 | return decorated_function 89 | 90 | 91 | def get_puzzle_bucket(request): 92 | token = request.args.get('token') 93 | 94 | if token is None: 95 | raise Error("token required") 96 | 97 | try: 98 | puzzle_bucket = PuzzleBucket.objects(public_token=token).get() 99 | except DoesNotExist: 100 | raise Error("Invalid token, please try again after checkin.") 101 | 102 | return puzzle_bucket 103 | 104 | 105 | def get_puzzle_bucket_by_attendee(attendee): 106 | try: 107 | puzzle_bucket = PuzzleBucket.objects(public_token=attendee.public_token).get() 108 | except DoesNotExist: 109 | raise Error("invalid token") 110 | 111 | return puzzle_bucket 112 | 113 | 114 | def get_attendee(request): 115 | token = request.args.get('token') 116 | 117 | if token is None: 118 | raise Error("token required") 119 | 120 | try: 121 | attendee = Attendee.objects(token=token).get() 122 | except DoesNotExist: 123 | raise Error("invalid token") 124 | 125 | return attendee 126 | 127 | 128 | @app.errorhandler(Error) 129 | def handle_error(error): 130 | response = jsonify(error.to_dict()) 131 | response.status_code = error.status_code 132 | return response 133 | 134 | 135 | @app.route('/landing') 136 | def landing(): 137 | attendee = get_attendee(request) 138 | 139 | return jsonify({"nickname": attendee.user_id}) 140 | 141 | 142 | @app.route('/status') 143 | @returns_json 144 | def status(): 145 | attendee = get_attendee(request) 146 | 147 | if not request.args.get('StaffQuery') and not attendee.first_use: 148 | attendee.first_use = time.time() 149 | attendee.save() 150 | 151 | PuzzleBucket.init(attendee) 152 | 153 | return attendee.to_json() 154 | 155 | 156 | @app.route('/use/') 157 | @returns_json 158 | def use(scenario_id): 159 | attendee = get_attendee(request) 160 | 161 | try: 162 | scenario = attendee.scenario[scenario_id] 163 | except KeyError: 164 | raise Error("invalid scenario_id") 165 | 166 | if scenario.available_time <= time.time() and scenario.expire_time > time.time(): 167 | if scenario.used is not None: 168 | raise Error("has been used") 169 | 170 | if scenario.disabled is not None: 171 | raise Error("disabled scenario") 172 | 173 | if scenarios_def[attendee.role].get(scenario_id).get('related_scenario'): 174 | for rsce in scenarios_def[attendee.role].get(scenario_id).get('related_scenario'): 175 | if rsce['unlock']: 176 | attendee.scenario[rsce['id']].disabled = None 177 | 178 | if request.args.get('StaffQuery') and rsce.get('staff_query_used') and attendee.scenario[rsce['id']].used is None: 179 | attendee.scenario[rsce['id']].used = time.time() 180 | 181 | if rsce.get('disable_time') and time.time() > datetime.strptime(rsce['disable_time'], "%Y/%m/%d %H:%M %z").timestamp(): 182 | attendee.scenario[rsce['id']].disabled = rsce['disable_message'] 183 | elif request.args.get('StaffQuery') and rsce.get('staff_query_disable_message'): 184 | attendee.scenario[rsce['id']].disabled = rsce['staff_query_disable_message'] 185 | 186 | if scenarios_def[attendee.role].get(scenario_id).get('deliver_puzzle') and puzzle_config is not None: 187 | for i in range(scenarios_def[attendee.role].get(scenario_id).get('deliver_puzzle')): 188 | deliver_puzzle(attendee) 189 | 190 | scenario.used = time.time() 191 | attendee.save() 192 | 193 | return get_attendee(request).to_json() 194 | else: 195 | raise Error("link expired/not available now") 196 | 197 | 198 | @app.route('/event/puzzle') 199 | def get_puzzle(): 200 | puzzle_bucket = get_puzzle_bucket(request) 201 | 202 | return jsonify({ 203 | "user_id": puzzle_bucket.attendee.user_id, 204 | "puzzles": puzzle_bucket.puzzle, 205 | "deliverers": puzzle_bucket.deliverer, 206 | "valid": puzzle_bucket.valid, 207 | "coupon": puzzle_bucket.coupon 208 | }) 209 | 210 | 211 | @app.route('/event/puzzle/revoke') 212 | def revoke_puzzle(): 213 | attendee = get_attendee(request) 214 | 215 | puzzle_bucket = get_puzzle_bucket_by_attendee(attendee) 216 | 217 | PuzzleStatus.objects(puzzle='total').update_one(dec__currency=len(puzzle_bucket.puzzle)) 218 | for puzzle in puzzle_bucket.puzzle: 219 | PuzzleStatus.objects(puzzle=puzzle).update_one(dec__currency=1) 220 | 221 | puzzle_bucket.valid = time.time() 222 | puzzle_bucket.coupon = 0 223 | 224 | puzzle_bucket.save() 225 | 226 | return jsonify({'status': 'OK'}) 227 | 228 | 229 | @app.route('/event/puzzle/coupon') 230 | def use_coupon(): 231 | attendee = get_attendee(request) 232 | 233 | puzzle_bucket = get_puzzle_bucket_by_attendee(attendee) 234 | 235 | puzzle_bucket.coupon = time.time() 236 | puzzle_bucket.save() 237 | 238 | return jsonify({'status': 'OK'}) 239 | 240 | 241 | @app.route('/event/puzzle/deliverer') 242 | def get_deliverer(): 243 | token = request.args.get('token') 244 | 245 | if token is None: 246 | raise Error("token required") 247 | 248 | if token in delivery_permission.keys(): 249 | return jsonify({'slug': delivery_permission[token]}) 250 | else: 251 | raise Error("invalid deliverer token") 252 | 253 | 254 | @app.route('/event/puzzle/deliverers') 255 | def get_deliverers(): 256 | return jsonify(list(delivery_permission.values())) 257 | 258 | 259 | @app.route('/event/puzzle/deliver', methods=['POST']) 260 | def do_deliver_puzzle(): 261 | token = request.args.get('token') 262 | receiver = request.form.get('receiver') 263 | 264 | if token is None or receiver is None: 265 | raise Error("token and receiver required") 266 | 267 | try: 268 | attendee = Attendee.objects(token=receiver).get() 269 | except DoesNotExist: 270 | raise Error("invalid receiver token") 271 | 272 | if token in delivery_permission.keys(): 273 | deliver_puzzle(attendee, delivery_permission[token]) 274 | app.logger.info(delivery_permission[token] + ' ' + token + ' deliver puzzle to ' + attendee.token) 275 | return jsonify({ 276 | 'status': 'OK', 277 | 'user_id': attendee.user_id 278 | }) 279 | else: 280 | raise Error("invalid token") 281 | 282 | 283 | @app.route('/event/puzzle/dashboard') 284 | @returns_json 285 | def get_puzzle_dashboard(): 286 | puzzle_status = PuzzleStatus.objects() 287 | 288 | return puzzle_status.to_json() 289 | 290 | 291 | @app.route('/announcement', methods=['GET', 'POST']) 292 | def announcement(): 293 | if request.method == 'GET': 294 | token = request.args.get('token') 295 | role = config.ANNOUNCEMENT_DEFAULT_ROLE 296 | 297 | if token is not None: 298 | try: 299 | attendee = Attendee.objects(token=token).get() 300 | role = attendee.role 301 | except DoesNotExist: 302 | role = config.ANNOUNCEMENT_DEFAULT_ROLE 303 | 304 | return Announcement.objects(role__in=[role]).order_by('-_id').to_json() 305 | 306 | if request.method == 'POST': 307 | announcement = Announcement() 308 | announcement.datetime = time.time() 309 | announcement.msg_zh = request.form.get('msg_zh') 310 | announcement.msg_en = request.form.get('msg_en') 311 | announcement.uri = request.form.get('uri') 312 | announcement.role = request.form.getlist('role[]') 313 | announcement.save() 314 | 315 | return jsonify({'status': 'OK'}) 316 | 317 | 318 | def role_stats(role): 319 | res = { 320 | 'role': role, 321 | 'total': Attendee.objects(role=role).count(), 322 | 'logged': Attendee.objects(role=role, first_use__ne=None).count(), 323 | 'scenarios': [] 324 | } 325 | 326 | for scenario in scenarios_def[role]: 327 | query_enabled_args = { 328 | 'role': role, 329 | 'scenario__{}__disabled'.format(scenario): None 330 | } 331 | 332 | query_used_args = { 333 | 'role': role, 334 | 'scenario__{}__used__ne'.format(scenario): None 335 | } 336 | 337 | res['scenarios'].append({ 338 | 'scenario': scenario, 339 | 'enabled': Attendee.objects(**query_enabled_args).count(), 340 | 'used': Attendee.objects(**query_used_args).count() 341 | }) 342 | 343 | return res 344 | 345 | 346 | @app.route('/dashboard') 347 | def dashboard(): 348 | return jsonify(list(map(role_stats, scenarios_def.keys()))) 349 | 350 | 351 | @app.route('/dashboard/') 352 | def dashboard_role(role): 353 | 354 | if role not in scenarios_def: 355 | raise Error('role required') 356 | 357 | scenarios = scenarios_def[role] 358 | 359 | req_fields = ['event_id', 'user_id', 'attr'] + \ 360 | list(map(lambda str: 'scenario__' + str + '__used', scenarios)) + \ 361 | list(map(lambda str: 'scenario__' + str + '__attr', scenarios)) \ 362 | 363 | return Attendee.objects(role=role).only(*req_fields).to_json() 364 | 365 | 366 | @app.route('/roles') 367 | def roles(): 368 | return jsonify(list(scenarios_def.keys())) 369 | 370 | 371 | @app.route('/scenarios') 372 | def scenarios(): 373 | try: 374 | return jsonify(list(scenarios_def[request.args.get('role')].keys())) 375 | except KeyError: 376 | raise Error("role required") 377 | -------------------------------------------------------------------------------- /app/config-sample.py: -------------------------------------------------------------------------------- 1 | MONGODB_SETTINGS = { 2 | 'db': 'ccip', 3 | 'host': '127.0.0.1', 4 | 'port': 27017 5 | } 6 | 7 | SCENARIO_DEFS = { 8 | "audience": "scenario.json", 9 | "staff": "scenario-staff.json" 10 | } 11 | 12 | ANNOUNCEMENT_DEFAULT_ROLE = "audience" 13 | -------------------------------------------------------------------------------- /app/delivery-permission-sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "DisplayName", 3 | "token1": "Sponsor", 4 | "token2": "Community" 5 | } 6 | -------------------------------------------------------------------------------- /app/error.py: -------------------------------------------------------------------------------- 1 | class Error(Exception): 2 | status_code = 400 3 | 4 | def __init__(self, message, status_code=None, payload=None): 5 | Exception.__init__(self) 6 | self.message = message 7 | if status_code is not None: 8 | self.status_code = status_code 9 | self.payload = payload 10 | 11 | def to_dict(self): 12 | rv = dict(self.payload or ()) 13 | rv['message'] = self.message 14 | return rv 15 | -------------------------------------------------------------------------------- /app/import.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import json 3 | import os 4 | 5 | from flask import Flask 6 | from models import db, Attendee, Scenario 7 | from datetime import datetime 8 | 9 | import config 10 | 11 | app = Flask(__name__) 12 | app.config.from_pyfile('config.py') 13 | 14 | scenarios_def = {} 15 | for role, filename in config.SCENARIO_DEFS.items(): 16 | with open(filename) as json_file: 17 | scenarios_def[role] = json.load(json_file) 18 | 19 | 20 | def str2timestamp(str): 21 | return datetime.strptime(str, "%Y/%m/%d %H:%M %z").timestamp() 22 | 23 | 24 | def bind_scenario(row, attendee, scenarios): 25 | for scenario_id, scenario in scenarios.items(): 26 | sce = Scenario() 27 | 28 | if scenario.get('show_rule'): 29 | if row[scenario.get('show_rule')['row_name']] != scenario.get('show_rule')['value_match']: 30 | continue 31 | 32 | sce.order = scenario['order'] 33 | sce.display_text = scenario['display_text'] 34 | sce.available_time = str2timestamp(scenario['available_time']) 35 | sce.expire_time = str2timestamp(scenario['expire_time']) 36 | sce.countdown = scenario['countdown'] 37 | 38 | if scenario.get('lock_message'): 39 | sce.disabled = scenario.get('lock_message') 40 | 41 | if scenario.get('attr'): 42 | for attr in scenario.get('attr'): 43 | if not attr.get('value'): 44 | sce.attr[attr['attr_name']] = row[attr['row_name']] 45 | 46 | else: 47 | sce.attr[attr['attr_name']] = attr.get('value')[row[attr['row_name']]] 48 | 49 | if scenario.get('not_lock_rule'): 50 | if row[scenario.get('not_lock_rule')['row_name']] == scenario.get('not_lock_rule')['value_match']: 51 | sce.disabled = None 52 | else: 53 | sce.disabled = scenario.get('not_lock_rule')['not_match_disable_message'] 54 | 55 | attendee.scenario[scenario_id] = sce 56 | 57 | attendee.save() 58 | 59 | 60 | def list_import(attendee_list, role): 61 | for row in attendee_list: 62 | attendee = Attendee() 63 | attendee.event_id = os.environ['EVENT_ID'] 64 | attendee.token = row['token'] 65 | 66 | try: 67 | attendee.attr['title'] = row['title'] 68 | except KeyError: 69 | pass 70 | attendee.user_id = row['display_name'] 71 | 72 | attendee.role = role 73 | 74 | bind_scenario(row, attendee, scenarios_def[role]) 75 | 76 | 77 | if __name__ == '__main__': 78 | import sys 79 | 80 | try: 81 | os.environ['EVENT_ID'] 82 | except KeyError: 83 | print("export EVENT_ID in env") 84 | exit(1) 85 | 86 | filename = sys.argv[1] 87 | role = sys.argv[2] 88 | 89 | with open(filename, 'r') as csv_file: 90 | list_import(csv.DictReader(csv_file), role) 91 | -------------------------------------------------------------------------------- /app/models/__init__.py: -------------------------------------------------------------------------------- 1 | from mongoengine import connect 2 | 3 | import config 4 | 5 | connect(**config.MONGODB_SETTINGS) 6 | 7 | from models.attendee import * 8 | from models.announcement import * 9 | from models.puzzle import * 10 | -------------------------------------------------------------------------------- /app/models/announcement.py: -------------------------------------------------------------------------------- 1 | import mongoengine as db 2 | 3 | 4 | class Announcement(db.Document): 5 | datetime = db.IntField() 6 | msg_zh = db.StringField() 7 | msg_en = db.StringField() 8 | uri = db.StringField() 9 | role = db.ListField() 10 | -------------------------------------------------------------------------------- /app/models/attendee.py: -------------------------------------------------------------------------------- 1 | import bson 2 | import mongoengine as db 3 | from hashlib import sha1 4 | 5 | 6 | class Scenario(db.EmbeddedDocument): 7 | order = db.IntField() 8 | display_text = db.DictField() 9 | available_time = db.IntField() 10 | expire_time = db.IntField() 11 | used = db.IntField() 12 | disabled = db.StringField() 13 | countdown = db.IntField() 14 | attr = db.DictField() 15 | 16 | 17 | class Attendee(db.Document): 18 | event_id = db.StringField() 19 | token = db.StringField(unique=True) 20 | user_id = db.StringField() 21 | scenario = db.DictField() 22 | attr = db.DictField() 23 | first_use = db.IntField() 24 | role = db.StringField() 25 | 26 | meta = { 27 | 'indexes': [ 28 | 'event_id', 29 | 'token', 30 | 'role' 31 | ] 32 | } 33 | 34 | @property 35 | def public_token(self): 36 | return sha1(self.token.encode('utf-8')).hexdigest() 37 | 38 | def to_json(self): 39 | data = self.to_mongo() 40 | 41 | scenarios = [] 42 | for k, v in data['scenario'].items(): 43 | v.pop('_cls') 44 | v['id'] = k 45 | scenarios.append(v) 46 | 47 | data.pop('scenario') 48 | data['scenarios'] = sorted(scenarios, key=lambda k: k['order']) 49 | return bson.json_util.dumps(data) 50 | -------------------------------------------------------------------------------- /app/models/puzzle.py: -------------------------------------------------------------------------------- 1 | from mongoengine import NotUniqueError 2 | 3 | import mongoengine as db 4 | from models import Attendee 5 | 6 | 7 | class PuzzleStatus(db.Document): 8 | puzzle = db.StringField() 9 | quantity = db.IntField(default=0) 10 | currency = db.IntField(default=0) 11 | 12 | meta = { 13 | 'indexes': [ 14 | 'puzzle' 15 | ] 16 | } 17 | 18 | 19 | class PuzzleBucket(db.Document): 20 | attendee = db.ReferenceField(Attendee) 21 | public_token = db.StringField(unique=True) 22 | puzzle = db.ListField() 23 | valid = db.IntField() 24 | coupon = db.IntField() 25 | deliverer = db.ListField() 26 | 27 | meta = { 28 | 'indexes': [ 29 | 'public_token' 30 | ] 31 | } 32 | 33 | @classmethod 34 | def init(cls, attendee): 35 | try: 36 | return PuzzleBucket.objects.create(attendee=attendee, public_token=attendee.public_token) 37 | except NotUniqueError: 38 | return PuzzleBucket.objects(public_token=attendee.public_token).get() 39 | -------------------------------------------------------------------------------- /app/puzzle-config-sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "if_elif_else": 4, 3 | ">": 5, 4 | "<": 4, 5 | "!=": 4, 6 | "==": 3, 7 | "=(指派)": 7, 8 | "while": 7, 9 | "整數(任意整數)": 9, 10 | "變數(任意數量變數)": 7, 11 | "+": 5, 12 | "-": 4, 13 | "*": 3, 14 | "/": 4 15 | } 16 | -------------------------------------------------------------------------------- /app/reg-sample.csv: -------------------------------------------------------------------------------- 1 | token,display_name,飲食,個人贊助 2 | "7679f08f7eaeef5e9a65a1738ae2840e","中文","葷",N 3 | "ee33c408df4ffad0c55eaf6ec2420717","test","素",N 4 | "fccfc8bfa07643a1ca8015cbe74f5f17","LINDA","葷",N 5 | "93dc46e553ac602b0d6c6d7307e523f1","BARBARA","素",N 6 | "2f574789271d26a0c569779e8348ab68","ELIZABETH","葷",N 7 | "1a04f8728ffc11a4cdb9bc7dc79caac0","JENNIFER","葷",N 8 | "d2ec0f1f28db8a4c1483c370953b9f0c","MARIA","素",N 9 | "6520d6fa2fee26045104db0364a092e9","SUSAN","葷",N 10 | "50ecac11d697bf76092bccde23607b27","MARGARET","素",N 11 | "125384b0058f0ce2d5aae8e80d94c5d7","DOROTHY","葷",N 12 | "087e78bb1460e7ddbafd6a3e004f095e","LISA","葷",N 13 | "d38c9001982e5601f2d37a0401528754","NANCY","素",N 14 | "5d765c2e8c5243c0adc3af8495841f5d","KAREN","葷",Y 15 | "16e1dc90555abf7b5bf5781882982755","BETTY","素",N 16 | "1aadcabf9f47e932ea6245cfd51c02e8","HELEN","葷",N 17 | "f1349e8156d2b05e23cf72d83a63c0f3","SANDRA","葷",N 18 | "bb0d74e19d0ce01d63af2bd6d44f06df","DONNA","素",N 19 | "8c654d02c35a8040228c3fef4b25e9d4","CAROL","葷",Y 20 | "17bcb36a975e125f9cbea503a54603f0","RUTH","素",N 21 | "0c4facff3bc9879061b4eddb631339fe","SHARON","葷",N 22 | "78e30e2397f1d8fda75eb930dd3dcf87","MICHELLE","葷",N 23 | "6a487514a78c67098054e07d1440c391","LAURA","素",Y 24 | "8a580a71b3c80199e6c7438618b4f030","SARAH","葷",N 25 | "c7847acc730ad24412b68f5c9f5da851","KIMBERLY","葷",N 26 | "aba94b39687829edef692a384dd698aa","DEBORAH","素",N 27 | "4ae0249680951e256954b0b67fddbcc1","JESSICA","葷",N 28 | "e9b4c7c9995d63993007a211b817ef68","SHIRLEY","素",N 29 | "222449320f2c14d54398f21bdbf253a2","CYNTHIA","葷",N 30 | "6edd6937afcabef71c0917d148811d54","ANGELA","葷",N 31 | "84b1701a3964056eb4bb85daee801522","MELISSA","素",N 32 | "307b11ad88ae3897aadd6af42ba78d2d","BRENDA","葷",N 33 | "a4f07c20edb3ca71ba1457f2d79d90b7","AMY","素",N 34 | "99a3afda0f76109b04ae157c38f9fcb6","ANNA","葷",N 35 | "8145928fa6eb669cc201afd6b52a7f98","REBECCA","葷",N 36 | "85e13879cd70ee72532dc8823dbb503f","VIRGINIA","素",N 37 | "48403a8ade71205cf92c1820deb7a962","KATHLEEN","葷",N 38 | "e041d7afabeb71d69ad6bdac96d95575","PAMELA","素",N 39 | "de0881a5824b9dba0925e5fdf15bf6bd","MARTHA","葷",N 40 | "4aa7cdc891898e1c74a810448f59102c","DEBRA","葷",N 41 | "ce41ae89b2ad073c6321bce05d563885","AMANDA","素",N 42 | "2d4acd203b335e400ef36ed4d34ee2e2","STEPHANIE","葷",N 43 | "e7f64b31340199985ee6803f8ab3b39e","CAROLYN","素",N 44 | "18eac5a3946862b8cd34e2d37c229b3d","CHRISTINE","葷",N 45 | "5382f8e7718f94736d850482e7ae8316","MARIE","葷",N 46 | "e64d153fa408cb4e74ea0bff3a7b4d2e","JANET","素",N 47 | "fc9db41221ac1b7403d185d58a44888e","CATHERINE","葷",N 48 | "b7edc7b7341bb77e611d8f1d0a643819","FRANCES","葷",N 49 | "c4f9bde7cdb2099961cce496a2f7b0a8","ANN","素",N 50 | "c33c66ae4a39134b348dc4b619dc66e4","JOYCE","葷",N 51 | "1c75cd1c124e2ede9aefb8b45a89adee","DIANE","素",N 52 | "df779a8bb35387d8792c8823b30ce9ce","ALICE","葷",N 53 | "36186f7d89d81e4961742ebeaa669314","JULIE","葷",N 54 | "9b7391abcc2384f7836f47bde388a961","HEATHER","素",N 55 | "3eb973079e7416b20a852ef96ebca612","TERESA","葷",N 56 | "9d869b8db3b79add3092642f2262f22a","DORIS","素",N 57 | "3f828284bb06d720cc8fede98db56bc6","GLORIA","葷",N 58 | "d56eabaf6b0709ff718cd5977b787a8b","EVELYN","葷",N 59 | "069b6e3c73e749684a7c34be84ecc547","JEAN","素",N 60 | -------------------------------------------------------------------------------- /app/scenario-sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "day1checkin": { 3 | "order": 0, 4 | "display_text": { 5 | "en-US": "Day 1 Check-in", 6 | "zh-TW": "Day 1 報到" 7 | }, 8 | "available_time": "2016/08/20 7:30 +0800", 9 | "expire_time": "2016/08/20 17:00 +0800", 10 | "countdown": 0, 11 | "related_scenario": [ 12 | { 13 | "id": "kit", 14 | "staff_query_used": true, 15 | "unlock": true 16 | }, 17 | { 18 | "id": "day1lunch", 19 | "disable_time": "2016/08/20 10:30 +0800", 20 | "disable_message": "Too late to check-in", 21 | "staff_query_disable_message": "Please use your badge", 22 | "unlock": true 23 | } 24 | ], 25 | "deliver_puzzle": 2 26 | }, 27 | "kit": { 28 | "order": 1, 29 | "display_text": { 30 | "en-US": "Welcome Kit", 31 | "zh-TW": "迎賓袋" 32 | }, 33 | "available_time": "2016/08/20 8:30 +0800", 34 | "expire_time": "2016/08/21 14:00 +0800", 35 | "countdown": 30, 36 | "lock_message": "Haven't Check-in", 37 | "attr": [ 38 | { 39 | "row_name": "shirt_size", 40 | "attr_name": "shirt_size" 41 | } 42 | ] 43 | }, 44 | "day1lunch": { 45 | "order": 2, 46 | "display_text": { 47 | "en-US": "Day 1 Lunch", 48 | "zh-TW": "Day 1 午餐" 49 | }, 50 | "available_time": "2016/08/20 12:00 +0800", 51 | "expire_time": "2016/08/21 14:00 +0800", 52 | "countdown": 30, 53 | "lock_message": "Haven't Check-in", 54 | "attr": [ 55 | { 56 | "row_name": "飲食", 57 | "attr_name": "diet", 58 | "value": { 59 | "素": "vegetarian", 60 | "葷": "meat" 61 | } 62 | } 63 | ] 64 | }, 65 | "day2checkin": { 66 | "order": 3, 67 | "display_text": { 68 | "en-US": "Day 2 Check-in", 69 | "zh-TW": "Day 2 報到" 70 | }, 71 | "available_time": "2016/08/21 7:30 +0800", 72 | "expire_time": "2016/08/21 17:00 +0800", 73 | "countdown": 0, 74 | "related_scenario": [ 75 | { 76 | "id": "kit", 77 | "staff_query_used": true, 78 | "unlock": true 79 | }, 80 | { 81 | "id": "day2lunch", 82 | "disable_time": "2016/08/21 10:30 +0800", 83 | "disable_message": "Too late to check-in", 84 | "staff_query_disable_message": "Please use your badge", 85 | "unlock": true 86 | } 87 | ] 88 | }, 89 | "day2lunch": { 90 | "order": 4, 91 | "display_text": { 92 | "en-US": "Day 2 Lunch", 93 | "zh-TW": "Day 2 午餐" 94 | }, 95 | "available_time": "2016/08/21 12:00 +0800", 96 | "expire_time": "2016/08/21 14:00 +0800", 97 | "countdown": 30, 98 | "lock_message": "Haven't Check-in", 99 | "attr": [ 100 | { 101 | "row_name": "飲食", 102 | "attr_name": "diet", 103 | "value": { 104 | "素": "vegetarian", 105 | "葷": "meat" 106 | } 107 | } 108 | ] 109 | }, 110 | "vipkit": { 111 | "order": 5, 112 | "display_text": { 113 | "en-US": "Special Gift", 114 | "zh-TW": "獨家紀念品" 115 | }, 116 | "available_time": "2016/08/20 8:15 +0800", 117 | "expire_time": "2016/08/21 14:00 +0800", 118 | "countdown": 30, 119 | "not_lock_rule": { 120 | "row_name": "個人贊助", 121 | "value_match": "Y", 122 | "not_match_disable_message": "For Supporters Only" 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /app/staff-sample.csv: -------------------------------------------------------------------------------- 1 | "id","username","groups","email","display_name","title","last_name","first_name","organization","avatar","phone","redmine","slack","twenty","certificate","cel_dinner","prev_worker","residence","shirt_size","diet","transportation_aid","transportation_hr","transportation","transportation_fee","accom","roommate","gender","volunteering_proof","volunteering_work_done","volunteering_duration","volunteering_time","birthday","language","abilities","bio","comment" 2 | "160","denny","工作人員,線路組","denny0223@gmail.com","DennyHuang","線路組員","黃","丹丹","","https://secure.gravatar.com/avatar/9c08215f31eb6005a25be6521bf47b0a?d=identicon","0922686223","denny0223","dennyhuang","TRUE","TRUE","TRUE","TRUE","臺北市","男M","葷","FALSE","FALSE","","","Either","","Male","FALSE","","","","","台語","","","" 3 | "160","denny1","工作人員,線路組","denny0223@gmail.com","DennyHua1g","線路組員","黃","丹丹壹","","https://secure.gravatar.com/avatar/9c08215f31eb6005a25be6521bf47b0a?d=identicon","0922686223","denny0223","dennyhuang","TRUE","TRUE","TRUE","TRUE","臺北市","男M","葷","FALSE","FALSE","","","Either","","Male","FALSE","","","","","台語","","","" 4 | "161","denny2","工作人員,場務組","denny0223@gmail.com","DennyHua2g","場務組員","黃","丹丹貳","","https://secure.gravatar.com/avatar/9c08215f31eb6005a25be6521bf47b0a?d=identicon","0922686223","denny0223","dennyhuang","TRUE","TRUE","TRUE","TRUE","臺北市","男M","葷","FALSE","FALSE","","","Either","","Male","FALSE","","","","","台語","","","" 5 | "162","denny3","工作人員,組長,丹丹組","denny0223@gmail.com","DennyHua3g","丹丹組長","黃","丹丹參","","https://secure.gravatar.com/avatar/9c08215f31eb6005a25be6521bf47b0a?d=identicon","0922686223","denny0223","dennyhuang","TRUE","TRUE","TRUE","TRUE","臺北市","男M","葷","FALSE","FALSE","","","Either","","Male","FALSE","","","","","台語","","","" 6 | "163","denny4","工作人員,線路組,資訊組","denny0223@gmail.com","DennyHua4g","線路組兼資訊組員","黃","丹丹肆","","https://secure.gravatar.com/avatar/9c08215f31eb6005a25be6521bf47b0a?d=identicon","0922686223","denny0223","dennyhuang","TRUE","TRUE","TRUE","TRUE","臺北市","男M","葷","FALSE","FALSE","","","Either","","Male","FALSE","","","","","台語","","","" 7 | -------------------------------------------------------------------------------- /app/webhook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script generates another client javascript which generates a CSV by the 4 | # JSON config you generated (now is defined in the script) from the KKTIX 5 | # attendee detail page. This script also provided a web server which accepts 6 | # client's report of the attendee details, then import to the CCIP database. 7 | 8 | # The reporting processing is using HTTP GET method, the format is below 9 | # 10 | # http://$server:$port/$token/$csvobject 11 | # 12 | # The token is increasing the security of the script, preventing someone sends 13 | # random shit into the system. The token should only be existing on the 14 | # certified client. 15 | 16 | # the CSV object is a normal CSV file content with the newline replacement 17 | # (with 😱). 18 | 19 | # The server will dump the CSV file to a temporary file, then import it to 20 | # the database. 21 | 22 | 23 | token='I_am_the_token' 24 | server='[server]' 25 | port='8000' 26 | gnu_grep='grep' 27 | md5_cmd='md5sum' 28 | #gnu_grep='ggrep' 29 | #md5_cmd='md5' 30 | 31 | # Active env 32 | # source ../env/bin/activate 33 | 34 | print_js(){ 35 | #Source file: 36 | # 37 | #(function(){ 38 | # keys = { 39 | # 'type': {'type': 'null'}, 40 | # 'token': {'type': 'qrcode'}, 41 | # 'id': {'type': 'field', 'value': '暱稱'}, 42 | # '飲食': {'type': 'field', 'value': '飲食習慣'}, 43 | # '個人贊助': {'type': 'value', 'value': 'Y'}, 44 | # 'price': {'type': 'price'}, 45 | # 'shirt_size': {'type': 'field', 'value': '回饋贈品衣服尺寸'} 46 | # }; 47 | # 48 | # function keys_by_type(type){ 49 | # field = {}; 50 | # for (k in keys){ 51 | # if (keys[k].type == type){ 52 | # field[k] = keys[k]; 53 | # } 54 | # } 55 | # return field; 56 | # } 57 | # 58 | # output = {}; 59 | # 60 | # // Field type 61 | # a = document.getElementsByClassName('control-group'); 62 | # a = Array.prototype.filter.call(a, b => b.classList.length == 2); 63 | # a = Array.prototype.filter.call(a, b => b.getElementsByClassName('control-label')[0].innerText.match(/^\n/) == null); 64 | # ks = keys_by_type('field'); 65 | # for (b of a){ 66 | # for (k in ks){ 67 | # if (b.getElementsByClassName('control-label')[0].innerText == ks[k].value){ 68 | # output[k] = b.getElementsByClassName('controls')[0].innerText; 69 | # } 70 | # } 71 | # } 72 | # 73 | # // Price type 74 | # ks = keys_by_type('price'); 75 | # for (k in ks){ 76 | # output[k] = document.getElementsByClassName('currency-value')[document.getElementsByClassName('currency-value').length-1].innerText.replace(',',''); 77 | # } 78 | # 79 | # // QRCode type 80 | # qrid = ''; 81 | # for (i of document.getElementsByTagName('img')){ 82 | # if (i.currentSrc.match(/^https:\/\/kktix\.com\/g\/qr/)){ 83 | # qrid = i.currentSrc.split('=').pop(); 84 | # } 85 | # } 86 | # ks = keys_by_type('qrcode'); 87 | # for (k in ks){ 88 | # output[k] = qrid; 89 | # } 90 | # 91 | # // value type 92 | # ks = keys_by_type('value'); 93 | # for (k in ks){ 94 | # output[k] = ks[k].value; 95 | # } 96 | # 97 | # // null type 98 | # ks = keys_by_type('null'); 99 | # for (k in ks){ 100 | # output[k] = ''; 101 | # } 102 | # 103 | # function convert2csvla(data){ 104 | # output = ''; 105 | # for (i in data){ 106 | # output += i + ','; 107 | # } 108 | # output = output.slice(0, -1) + '\n'; 109 | # for (i in data){ 110 | # output += data[i] + ','; 111 | # } 112 | # output = output.slice(0, -1); 113 | # return output; 114 | # } 115 | # 116 | # url = 'http://$server:$port/$token/' + convert2csvla(output).replace('\n','😱'); 117 | # window.open(url); 118 | #})() 119 | 120 | # By Minifer https://javascript-minifier.com/ 121 | echo "javascript:!function(){function e(e){for(k in field={},keys)keys[k].type==e&&(field[k]=keys[k]);return field}for(b of(keys={type:{type:'null'},token:{type:'qrcode'},id:{type:'field',value:'暱稱'},'飲食':{type:'field',value:'飲食習慣'},'個人贊助':{type:'value',value:'Y'},price:{type:'price'},shirt_size:{type:'field',value:'回饋贈品衣服尺寸'}},output={},a=document.getElementsByClassName('control-group'),a=Array.prototype.filter.call(a,e=>2==e.classList.length),a=Array.prototype.filter.call(a,e=>null==e.getElementsByClassName('control-label')[0].innerText.match(/^\n/)),ks=e('field'),a))for(k in ks)b.getElementsByClassName('control-label')[0].innerText==ks[k].value&&(output[k]=b.getElementsByClassName('controls')[0].innerText);for(k in ks=e('price'),ks)output[k]=document.getElementsByClassName('currency-value')[document.getElementsByClassName('currency-value').length-1].innerText.replace(',','');for(i of(qrid='',document.getElementsByTagName('img')))i.currentSrc.match(/^https:\/\/kktix\.com\/g\/qr/)&&(qrid=i.currentSrc.split('=').pop());for(k in ks=e('qrcode'),ks)output[k]=qrid;for(k in ks=e('value'),ks)output[k]=ks[k].value;for(k in ks=e('null'),ks)output[k]='';url='http://$server:$port/$token/'+function(e){for(i in output='',e)output+=i+',';for(i in output=output.slice(0,-1)+'\n',e)output+=e[i]+',';return output=output.slice(0,-1),output}(output).replace('\n','😱'),window.open(url)}();" 122 | } 123 | 124 | print_instruction(){ 125 | echo "1. Copy the following code to a new bookmark" 126 | echo "2. Enter the detail of the ticket" 127 | echo "3. Click the bookmark" 128 | echo 129 | echo "===================code===================" 130 | print_js 131 | echo "===================code===================" 132 | } 133 | 134 | 135 | urldecode(){ 136 | [ -n "$1" ] && python2 -c "import sys, urllib as ul; print ul.unquote_plus(sys.argv[1])" $1 137 | } 138 | 139 | token=$(echo $token | $md5_cmd | awk '{print $1}') 140 | print_instruction 141 | 142 | while true 143 | do 144 | req=$(urldecode $(echo -e 'HTTP/1.1 200 OK\r\n\r\n' | nc -l $port | head -n1 | $gnu_grep -oP '/\K.*\ ')) # Must be GNU Grep 145 | req_token=$(echo $req | $gnu_grep -oP '\K.*(?=/)' ) 146 | req_data=$(echo $req | $gnu_grep -oP '/\K.*') 147 | if [ "$req_token" == "$token" ] 148 | then 149 | [ -n "$req" ] && echo $req_data | sed 's/😱/\n/g' > /tmp/attendee_$$.csv && cat /tmp/attendee_$$.csv && python import.py /tmp/attendee_$$.csv scenario-attendee.json 150 | else 151 | echo wrong_token 152 | fi 153 | done 154 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | backend: 3 | container_name: ccip_server 4 | image: ccip-app/ccip-server:latest 5 | ports: 6 | - 5000:5000 7 | # volumes: 8 | # - ./docker-entrypoint.sh:/app/docker-entrypoint.sh 9 | -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | /root/.local/bin/poetry run waitress-serve --port=5000 ccip:app 2 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 443; 3 | listen [::]:443; 4 | server_name hitcon.opass.app; 5 | 6 | ssl on; 7 | ssl_certificate /etc/letsencrypt/live/host_name/fullchain.pem; 8 | ssl_certificate_key /etc/letsencrypt/live/host_name/privkey.pem; 9 | 10 | ssl_session_timeout 5m; 11 | 12 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 13 | ssl_prefer_server_ciphers on; 14 | ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; 15 | 16 | add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; 17 | add_header X-Content-Type-Options nosniff; 18 | add_header 'Access-Control-Allow-Origin' '*' always; 19 | add_header 'Access-Control-Allow-Methods' 'GET, POST' always; 20 | add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,ccip'; 21 | 22 | ssl_stapling on; # Requires nginx >= 1.3.7 23 | ssl_stapling_verify on; # Requires nginx => 1.3.7 24 | 25 | location / { 26 | index index.html; 27 | root /usr/share/nginx/html/OPass-Landing/public; 28 | } 29 | 30 | location /puzzle { 31 | index index.html; 32 | alias /usr/share/nginx/html/CCIP-Puzzle-Bueno; 33 | } 34 | 35 | location /admin { 36 | auth_basic "Enter your password"; 37 | auth_basic_user_file /etc/nginx/.htpasswd; 38 | index index.html; 39 | alias /usr/share/nginx/html/CCIP-Admin-Bueno/dist; 40 | } 41 | 42 | location /announcement { 43 | if ($request_method = GET) { 44 | set $test A; 45 | } 46 | 47 | if ($request_method = OPTIONS) { 48 | set $test A; 49 | } 50 | 51 | if ($http_ccip = 'password') { 52 | set $test A; 53 | } 54 | 55 | if ($test != A) { 56 | return 403; 57 | } 58 | 59 | proxy_pass http://localhost:5000; 60 | } 61 | 62 | location /roles { 63 | proxy_pass http://localhost:5000; 64 | } 65 | 66 | location /scenarios { 67 | proxy_pass http://localhost:5000; 68 | } 69 | 70 | location /landing { 71 | proxy_pass http://localhost:5000; 72 | } 73 | 74 | location /status { 75 | # Whitelist IPs, for check user in the venue 76 | # allow 127.0.0.1/8; 77 | # deny all; 78 | 79 | expires 0; 80 | 81 | proxy_pass http://localhost:5000; 82 | } 83 | 84 | location /use { 85 | # Whitelist IPs, for check user in the venue 86 | # allow 127.0.0.1/8; 87 | # deny all; 88 | 89 | expires 0; 90 | 91 | proxy_pass http://localhost:5000; 92 | } 93 | 94 | location /event { 95 | expires 0; 96 | 97 | proxy_pass http://localhost:5000; 98 | } 99 | 100 | location /webhook { 101 | expires 0; 102 | 103 | proxy_pass http://localhost:8080; 104 | } 105 | 106 | location /dashboard { 107 | if ($request_method = 'OPTIONS') { return 200; } 108 | if ($http_ccip != 'password') { 109 | return 403; 110 | } 111 | expires 0; 112 | 113 | proxy_pass http://localhost:5000; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "blinker" 5 | version = "1.9.0" 6 | description = "Fast, simple object-to-object and broadcast signaling" 7 | optional = false 8 | python-versions = ">=3.9" 9 | groups = ["main"] 10 | files = [ 11 | {file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"}, 12 | {file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"}, 13 | ] 14 | 15 | [[package]] 16 | name = "click" 17 | version = "8.1.8" 18 | description = "Composable command line interface toolkit" 19 | optional = false 20 | python-versions = ">=3.7" 21 | groups = ["main"] 22 | files = [ 23 | {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, 24 | {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, 25 | ] 26 | 27 | [package.dependencies] 28 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 29 | 30 | [[package]] 31 | name = "colorama" 32 | version = "0.4.6" 33 | description = "Cross-platform colored terminal text." 34 | optional = false 35 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 36 | groups = ["main"] 37 | markers = "platform_system == \"Windows\"" 38 | files = [ 39 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 40 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 41 | ] 42 | 43 | [[package]] 44 | name = "dnspython" 45 | version = "2.7.0" 46 | description = "DNS toolkit" 47 | optional = false 48 | python-versions = ">=3.9" 49 | groups = ["main"] 50 | files = [ 51 | {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, 52 | {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, 53 | ] 54 | 55 | [package.extras] 56 | dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.16.0)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "quart-trio (>=0.11.0)", "sphinx (>=7.2.0)", "sphinx-rtd-theme (>=2.0.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] 57 | dnssec = ["cryptography (>=43)"] 58 | doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] 59 | doq = ["aioquic (>=1.0.0)"] 60 | idna = ["idna (>=3.7)"] 61 | trio = ["trio (>=0.23)"] 62 | wmi = ["wmi (>=1.5.1)"] 63 | 64 | [[package]] 65 | name = "flake8" 66 | version = "6.1.0" 67 | description = "the modular source code checker: pep8 pyflakes and co" 68 | optional = false 69 | python-versions = ">=3.8.1" 70 | groups = ["dev"] 71 | files = [ 72 | {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, 73 | {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, 74 | ] 75 | 76 | [package.dependencies] 77 | mccabe = ">=0.7.0,<0.8.0" 78 | pycodestyle = ">=2.11.0,<2.12.0" 79 | pyflakes = ">=3.1.0,<3.2.0" 80 | 81 | [[package]] 82 | name = "flask" 83 | version = "3.1.0" 84 | description = "A simple framework for building complex web applications." 85 | optional = false 86 | python-versions = ">=3.9" 87 | groups = ["main"] 88 | files = [ 89 | {file = "flask-3.1.0-py3-none-any.whl", hash = "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136"}, 90 | {file = "flask-3.1.0.tar.gz", hash = "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac"}, 91 | ] 92 | 93 | [package.dependencies] 94 | blinker = ">=1.9" 95 | click = ">=8.1.3" 96 | itsdangerous = ">=2.2" 97 | Jinja2 = ">=3.1.2" 98 | Werkzeug = ">=3.1" 99 | 100 | [package.extras] 101 | async = ["asgiref (>=3.2)"] 102 | dotenv = ["python-dotenv"] 103 | 104 | [[package]] 105 | name = "itsdangerous" 106 | version = "2.2.0" 107 | description = "Safely pass data to untrusted environments and back." 108 | optional = false 109 | python-versions = ">=3.8" 110 | groups = ["main"] 111 | files = [ 112 | {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, 113 | {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, 114 | ] 115 | 116 | [[package]] 117 | name = "jinja2" 118 | version = "3.1.5" 119 | description = "A very fast and expressive template engine." 120 | optional = false 121 | python-versions = ">=3.7" 122 | groups = ["main"] 123 | files = [ 124 | {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, 125 | {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, 126 | ] 127 | 128 | [package.dependencies] 129 | MarkupSafe = ">=2.0" 130 | 131 | [package.extras] 132 | i18n = ["Babel (>=2.7)"] 133 | 134 | [[package]] 135 | name = "markupsafe" 136 | version = "3.0.2" 137 | description = "Safely add untrusted strings to HTML/XML markup." 138 | optional = false 139 | python-versions = ">=3.9" 140 | groups = ["main"] 141 | files = [ 142 | {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, 143 | {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, 144 | {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, 145 | {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, 146 | {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, 147 | {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, 148 | {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, 149 | {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, 150 | {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, 151 | {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, 152 | {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, 153 | {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, 154 | {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, 155 | {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, 156 | {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, 157 | {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, 158 | {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, 159 | {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, 160 | {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, 161 | {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, 162 | {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, 163 | {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, 164 | {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, 165 | {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, 166 | {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, 167 | {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, 168 | {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, 169 | {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, 170 | {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, 171 | {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, 172 | {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, 173 | {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, 174 | {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, 175 | {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, 176 | {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, 177 | {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, 178 | {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, 179 | {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, 180 | {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, 181 | {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, 182 | {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, 183 | {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, 184 | {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, 185 | {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, 186 | {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, 187 | {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, 188 | {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, 189 | {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, 190 | {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, 191 | {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, 192 | {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, 193 | {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, 194 | {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, 195 | {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, 196 | {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, 197 | {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, 198 | {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, 199 | {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, 200 | {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, 201 | {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, 202 | {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, 203 | ] 204 | 205 | [[package]] 206 | name = "mccabe" 207 | version = "0.7.0" 208 | description = "McCabe checker, plugin for flake8" 209 | optional = false 210 | python-versions = ">=3.6" 211 | groups = ["dev"] 212 | files = [ 213 | {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, 214 | {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, 215 | ] 216 | 217 | [[package]] 218 | name = "mongoengine" 219 | version = "0.29.1" 220 | description = "MongoEngine is a Python Object-Document Mapper for working with MongoDB." 221 | optional = false 222 | python-versions = ">=3.7" 223 | groups = ["main"] 224 | files = [ 225 | {file = "mongoengine-0.29.1-py3-none-any.whl", hash = "sha256:9302ec407dd60f47f62cc07684d9f6cac87f1e93283c54203851788104d33df4"}, 226 | {file = "mongoengine-0.29.1.tar.gz", hash = "sha256:3b43abaf2d5f0b7d39efc2b7d9e78f4d4a5dc7ce92b9889ba81a5a9b8dee3cf3"}, 227 | ] 228 | 229 | [package.dependencies] 230 | pymongo = ">=3.4,<5.0" 231 | 232 | [package.extras] 233 | test = ["Pillow (>=7.0.0)", "blinker", "coverage", "pytest", "pytest-cov"] 234 | 235 | [[package]] 236 | name = "pycodestyle" 237 | version = "2.11.1" 238 | description = "Python style guide checker" 239 | optional = false 240 | python-versions = ">=3.8" 241 | groups = ["dev"] 242 | files = [ 243 | {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, 244 | {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, 245 | ] 246 | 247 | [[package]] 248 | name = "pyflakes" 249 | version = "3.1.0" 250 | description = "passive checker of Python programs" 251 | optional = false 252 | python-versions = ">=3.8" 253 | groups = ["dev"] 254 | files = [ 255 | {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, 256 | {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, 257 | ] 258 | 259 | [[package]] 260 | name = "pymongo" 261 | version = "4.11.1" 262 | description = "Python driver for MongoDB " 263 | optional = false 264 | python-versions = ">=3.9" 265 | groups = ["main"] 266 | files = [ 267 | {file = "pymongo-4.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e596caec72db62a3f438559dfa46d22faefea1967279f553f936ddcb873903df"}, 268 | {file = "pymongo-4.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15a88b25efcd61c5e539e9204932849b20f393efa330771676e860c4466fe8ad"}, 269 | {file = "pymongo-4.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7073a740aad257f9d2c12cb95a08f17db1f273d422e7ddfed9895738571cac7"}, 270 | {file = "pymongo-4.11.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25b7cadae1d5287b2eed3d901a347f3fa9bc3f898532e1cb7f28a1c9237d824d"}, 271 | {file = "pymongo-4.11.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3fe9589d9a83f6e2abe88f32daa410276eddd038eb8f8f75975cf8ce834cea1f"}, 272 | {file = "pymongo-4.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cc6d48b74e9abe544dd71b000453ad06e65cbfcfd57c7342a9f012f65532eb2"}, 273 | {file = "pymongo-4.11.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1518931a4a26d3cb31a97b9187087c6378cd0b0401d7a7cc160e92223a2a3059"}, 274 | {file = "pymongo-4.11.1-cp310-cp310-win32.whl", hash = "sha256:163c887384cb9fd16e0463128600867138a5a9a5344fc0903db08494b39a2d6e"}, 275 | {file = "pymongo-4.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:e147e08df329a7d23cbcb6213bc2fd360e51551626be828092fe2027f3473abc"}, 276 | {file = "pymongo-4.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ac125f2782d8fe3f3ff93a396af5482d694093b3be3e06052197096c83acadc"}, 277 | {file = "pymongo-4.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:681806d3ecaf29b11e16a45c1f4c28f99d9d8283238f7b6ea9eee93b5d7bc6d2"}, 278 | {file = "pymongo-4.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50210249a9bf67937e97205a312b96a4b1250b111cbaaff532d7a61bc2b1562d"}, 279 | {file = "pymongo-4.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdd0e404d5c3b1203ee61fcfee40a1f062f3780ce272febdc2378797b00401d1"}, 280 | {file = "pymongo-4.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6e46bcd3c2f86f442b721551ed5e5812294e4a93fce42517e173bd41d4cd2d8"}, 281 | {file = "pymongo-4.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f28d179e7d434869e23f4582c941cb400f75e996cfea472693ec756ee213c685"}, 282 | {file = "pymongo-4.11.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b56dbb6883ce7adad8588464948e0723a3d881e5549f48c4767f1654e8e4cb7d"}, 283 | {file = "pymongo-4.11.1-cp311-cp311-win32.whl", hash = "sha256:27bc58e0b1bebb17d2426d0cc191c579f2eeaf9692be880f93fe4180cf850ca7"}, 284 | {file = "pymongo-4.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:7751e6e99c79057b09441c6ab2a93fae10b4028478aac5b455db8b12f884a3c0"}, 285 | {file = "pymongo-4.11.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f96683f1dec7d28f12fe43a4d5c0df35d6b80348a9fbf5aac47fa284332a1f92"}, 286 | {file = "pymongo-4.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:157e6a722d051c4bab3e6bc34a1f80fc98101cf2d12139a94e51638d023198c5"}, 287 | {file = "pymongo-4.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74503e853758e1eaa1cad2df9c08c8c35a3d26222cf6426d2cde4b2e8593b9b3"}, 288 | {file = "pymongo-4.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b630596089106c968ddd252bde3fe692c420e24f214dd39ca517d26343d81012"}, 289 | {file = "pymongo-4.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7007669eef871079d39a9bbcda0fbcd4252f9b575592804343d0b5c05849d65b"}, 290 | {file = "pymongo-4.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d1da6201e1350cfcd4deab599b32237ac2ac591180d44553a2c8e614f2c0e"}, 291 | {file = "pymongo-4.11.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:908e65ab42cd4bf1ffeaafe8f11bb86b3f804d54227058794e33fff2963ccc86"}, 292 | {file = "pymongo-4.11.1-cp312-cp312-win32.whl", hash = "sha256:2d1d956c15dd05f1e41c61f0dbcaec59f274db4814cff2c3d9c2508f58004c39"}, 293 | {file = "pymongo-4.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:c71655f4188c70032ba56ac7ead688449e4f86a4ccd8e57201ee283f2f591e1d"}, 294 | {file = "pymongo-4.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f845b46d77a5bcf0c9ee16f11c5bc84c63f4668d9ea4fc54cd923c8d48a1d521"}, 295 | {file = "pymongo-4.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aadea45e01103f6ee4e80d76d4a27393a4e2bd93472ce4ebb894781f395e1053"}, 296 | {file = "pymongo-4.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a63348c850df796199abef7e9afbd86c34449f56731c7ec70b3901df1f5c135b"}, 297 | {file = "pymongo-4.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7dd7656794bfbfbe10723813332ec33eed29bd9bb7fc122c63829fd445eb8425"}, 298 | {file = "pymongo-4.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7146ae04300ce6f83b75c639e97c3d0ce873f30edaac4b719ae173e886b9ff90"}, 299 | {file = "pymongo-4.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698fb3d13126c0719077c98b40378cb9a6f4ab4a72b7691779aa01f1f6c66493"}, 300 | {file = "pymongo-4.11.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f415d9569720f408cc4dcc171f60299d454b0414f120666e6fdd349d414bf010"}, 301 | {file = "pymongo-4.11.1-cp313-cp313-win32.whl", hash = "sha256:4aa2c40e391ca29a337bef2b46b495c3f24b5696a87a58f0a0676a8bf131f9f8"}, 302 | {file = "pymongo-4.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:1f871efa14a1f368559edff39ec03799ca108bfa8e1ba330b7ffc05eb958661f"}, 303 | {file = "pymongo-4.11.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d293cec18624825937bd7f1d8bacf16104c79ced45a8ada93f08ec8a7a2ad17a"}, 304 | {file = "pymongo-4.11.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7b3ea3494f3e166a524529bb05a4fdda97afd77031fed3a63862fd815288c9df"}, 305 | {file = "pymongo-4.11.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d12f4c4579076b7351c63378e22f43d4ce4ed4f2c93208b653c4752f18f47309"}, 306 | {file = "pymongo-4.11.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a8aba4818350d2a463e084ae2426d395e725525fe86bd0219240b265dc1ca52"}, 307 | {file = "pymongo-4.11.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f97f62e6edde15d1d3d08abd7e43f1787ee9e672b1bb8e9d9f5fd6ded24f5599"}, 308 | {file = "pymongo-4.11.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a4e82dce301c97bb132dec28a487c1a609dc67948e9db7cbd23485875367204"}, 309 | {file = "pymongo-4.11.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:985a614ec24519f4a3d82aafb766c3f782a452fc46b32112d508a4e19b33fff3"}, 310 | {file = "pymongo-4.11.1-cp313-cp313t-win32.whl", hash = "sha256:889d20850d5aaa4f19814462c06488553e70ed4c62195dbaad5d5662884778af"}, 311 | {file = "pymongo-4.11.1-cp313-cp313t-win_amd64.whl", hash = "sha256:3854db4be39cb9e0c34add1fd7e515deab0b4ee30f3cc3978e057746d119ac12"}, 312 | {file = "pymongo-4.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:61f9a7ca6eb47378809c94cd8fbdbc5ee90c4bbb0c18ddf5592d25ed95cf939c"}, 313 | {file = "pymongo-4.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3b01623eb4a7ac58706e1920a94fbb47465f8ee19e7fbbb077e1707e37678863"}, 314 | {file = "pymongo-4.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2737ad54f0cd38e19ebf76e6f34dbbc6927615a2973425e64475d15a65fc2f6b"}, 315 | {file = "pymongo-4.11.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d7f291245c1688655aa308bbba7c9afa8116692c4fa6ad2646a835ed277a67b"}, 316 | {file = "pymongo-4.11.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:892f2137282a0a993d342db6e4e6dc2f3db0b771831c2d505f7055c52c023198"}, 317 | {file = "pymongo-4.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:822a73d22970978a6e55751d53eb0948521fc8e1380e306b8644096b5230412f"}, 318 | {file = "pymongo-4.11.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18b669e15922316e25a318cf9ba594eae5a6c24285a70f455ea01571d70a47d2"}, 319 | {file = "pymongo-4.11.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e7bac5fb1383a0df8b6881046207da20deb582a54e70c4c53ac9d4bbce323a3"}, 320 | {file = "pymongo-4.11.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:34d8b0ee57ad2a07ecdccec06269a4530767c2befb68f4a185113c866ad20b00"}, 321 | {file = "pymongo-4.11.1-cp39-cp39-win32.whl", hash = "sha256:490d3fd8006154894319af3a974764bf16baea87100222779f49c75cd8b16d3d"}, 322 | {file = "pymongo-4.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:1ed3c885ac221ddebd3e894aeae7b6bd84e7dbd4fd59f03e551d8f51455c7e9b"}, 323 | {file = "pymongo-4.11.1.tar.gz", hash = "sha256:3757ce9257c3486eead45680a8895a0ed9ba27efaf1791fc0cf854367c21c638"}, 324 | ] 325 | 326 | [package.dependencies] 327 | dnspython = ">=1.16.0,<3.0.0" 328 | 329 | [package.extras] 330 | aws = ["pymongo-auth-aws (>=1.1.0,<2.0.0)"] 331 | docs = ["furo (==2024.8.6)", "readthedocs-sphinx-search (>=0.3,<1.0)", "sphinx (>=5.3,<9)", "sphinx-autobuild (>=2020.9.1)", "sphinx-rtd-theme (>=2,<4)", "sphinxcontrib-shellcheck (>=1,<2)"] 332 | encryption = ["certifi ; os_name == \"nt\" or sys_platform == \"darwin\"", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.12.0,<2.0.0)"] 333 | gssapi = ["pykerberos ; os_name != \"nt\"", "winkerberos (>=0.5.0) ; os_name == \"nt\""] 334 | ocsp = ["certifi ; os_name == \"nt\" or sys_platform == \"darwin\"", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] 335 | snappy = ["python-snappy"] 336 | test = ["pytest (>=8.2)", "pytest-asyncio (>=0.24.0)"] 337 | zstd = ["zstandard"] 338 | 339 | [[package]] 340 | name = "waitress" 341 | version = "3.0.2" 342 | description = "Waitress WSGI server" 343 | optional = false 344 | python-versions = ">=3.9.0" 345 | groups = ["main"] 346 | files = [ 347 | {file = "waitress-3.0.2-py3-none-any.whl", hash = "sha256:c56d67fd6e87c2ee598b76abdd4e96cfad1f24cacdea5078d382b1f9d7b5ed2e"}, 348 | {file = "waitress-3.0.2.tar.gz", hash = "sha256:682aaaf2af0c44ada4abfb70ded36393f0e307f4ab9456a215ce0020baefc31f"}, 349 | ] 350 | 351 | [package.extras] 352 | docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.9)"] 353 | testing = ["coverage (>=7.6.0)", "pytest", "pytest-cov"] 354 | 355 | [[package]] 356 | name = "werkzeug" 357 | version = "3.1.3" 358 | description = "The comprehensive WSGI web application library." 359 | optional = false 360 | python-versions = ">=3.9" 361 | groups = ["main"] 362 | files = [ 363 | {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, 364 | {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, 365 | ] 366 | 367 | [package.dependencies] 368 | MarkupSafe = ">=2.1.1" 369 | 370 | [package.extras] 371 | watchdog = ["watchdog (>=2.3)"] 372 | 373 | [metadata] 374 | lock-version = "2.1" 375 | python-versions = "^3.12" 376 | content-hash = "ce071b0981f1be94afcb50575ece1fadefd02e06502ecbcff305313033727aa7" 377 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | package-mode = false 3 | name = "ccip-server" 4 | version = "1.0.0" 5 | description = "" 6 | authors = ["OPass Dev Team "] 7 | license = "AGPL-3.0" 8 | readme = "README.md" 9 | 10 | [tool.poetry.dependencies] 11 | python = "^3.12" 12 | mongoengine = "^0.29.1" 13 | Flask = "^3.1.0" 14 | waitress = "^3.0.2" 15 | 16 | [tool.poetry.group.dev.dependencies] 17 | flake8 = "^6.0.0" 18 | 19 | [build-system] 20 | requires = ["poetry-core"] 21 | build-backend = "poetry.core.masonry.api" 22 | -------------------------------------------------------------------------------- /uwsgi.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | chdir = /usr/share/nginx/CCIP-Server 3 | module = ccip 4 | callable = app 5 | venv = /usr/share/nginx/CCIP-Server/env/ 6 | socket = /usr/share/nginx/CCIP-Server/ccip.sock 7 | chown-socket = nginx:nginx 8 | vacuum = true 9 | processes = 8 10 | logto = /var/log/uwsgi/ccip.log 11 | lazy-apps = true 12 | --------------------------------------------------------------------------------