├── .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 |
--------------------------------------------------------------------------------