├── .gitignore ├── LICENSE.txt ├── README.md ├── chat_app.drawio.png ├── django-chat-app.gif ├── frontend ├── .env.development ├── .env.production ├── .gitignore ├── .vscode │ └── extensions.json ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ └── sounds │ │ └── iphone-ringtone-47958.mp3 ├── src │ ├── App.vue │ ├── assets │ │ ├── icons │ │ │ ├── check.svg │ │ │ ├── edit-3.svg │ │ │ ├── girl.svg │ │ │ ├── log-out.svg │ │ │ ├── message-circle.svg │ │ │ ├── more-vertical.svg │ │ │ ├── phone.svg │ │ │ ├── search.svg │ │ │ ├── settings.svg │ │ │ ├── user.svg │ │ │ └── video.svg │ │ └── logo.png │ ├── axios.js │ ├── components │ │ ├── ChatApp.vue │ │ ├── ChatWindow.vue │ │ ├── DefaultMessageWindow.vue │ │ ├── HelloWorld.vue │ │ └── buttons │ │ │ ├── MicButton.vue │ │ │ └── VideoButton.vue │ ├── main.js │ ├── router │ │ └── index.js │ ├── store │ │ └── index.js │ ├── styles │ │ ├── main.scss │ │ └── pages │ │ │ ├── _call.scss │ │ │ ├── _chat.scss │ │ │ └── _login.scss │ └── views │ │ ├── app │ │ ├── Layout.vue │ │ ├── call-app │ │ │ ├── BaseCallView.vue │ │ │ ├── Receiver.vue │ │ │ └── Sender.vue │ │ └── chat-app │ │ │ ├── ChatApp.vue │ │ │ ├── particals │ │ │ ├── leftbar │ │ │ │ ├── Header.vue │ │ │ │ ├── Search.vue │ │ │ │ └── UserList.vue │ │ │ └── rightbar │ │ │ │ ├── ActiveUser.vue │ │ │ │ └── MessageWindow.vue │ │ │ └── store.js │ │ └── auth │ │ ├── ForgetPassword.vue │ │ ├── Layout.vue │ │ ├── Login.vue │ │ ├── Registration.vue │ │ └── ResetPassword.vue └── vite.config.js ├── package-lock.json └── server ├── .gitignore ├── Pipfile ├── Pipfile.lock ├── chat_app ├── __init__.py ├── admin.py ├── apps.py ├── authentication.py ├── consumers │ ├── __init__.py │ ├── message.py │ └── notification.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_alter_profile_photo_message.py │ └── __init__.py ├── models.py ├── serializers.py ├── tests.py ├── urls.py ├── views │ ├── __init__.py │ ├── auth_view.py │ ├── call_view.py │ └── message_view.py └── ws_urls.py ├── config ├── __init__.py ├── asgi.py ├── routing.py ├── settings.py ├── urls.py └── wsgi.py ├── manage.py ├── media ├── dr_assistant_pro_customization.drawio.png └── girl.svg └── templates └── test.html /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/* 2 | !.vscode/extensions.json 3 | .idea 4 | .DS_Store 5 | *.suo 6 | *.ntvs* 7 | *.njsproj 8 | *.sln 9 | *.sw? 10 | __pycache__/ 11 | *.py[cod] 12 | *.sqlite3 -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 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) 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 637 | by 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django Chat App 2 | 3 | ![](https://github.com/kmrifat/django_chat/blob/master/django-chat-app.gif?raw=true) 4 | 5 | A private messaging and calling application build using Django, Django Channel and Peer.js (WebRTC). 6 | The front end has been build in Vue.js and I use Django Rest Framework for communicate with Backend and Frontend. 7 | 8 | PyWebsocket is been used inside Django Channel Wrapper for realtime communication. 9 | WebRTC is been used for Video and Audio calling. 10 | 11 | 12 | # Architecture 13 | 14 | ![](https://github.com/kmrifat/django_chat/blob/master/chat_app.drawio.png?raw=true) 15 | 16 | # Installation 17 | ``` 18 | git clone https://github.com/kmrifat/django_chat.git 19 | ``` 20 | 21 | ### FrontEnd Installation 22 | ``` 23 | cd frontend 24 | npm install 25 | npm run dev 26 | ``` 27 | 28 | ### Backend Installation 29 | ``` 30 | cd server 31 | pipenv shell 32 | pipenv install 33 | python manage.py migrate 34 | python manage.py runserver 35 | ``` 36 | Spacial Thanks to JetBrains Logo (Main) logo. For Awesome IDE support. 37 | -------------------------------------------------------------------------------- /chat_app.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmrifat/django_chat/e50cd269c58150ca30d26740b715bbd76cebb856/chat_app.drawio.png -------------------------------------------------------------------------------- /django-chat-app.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmrifat/django_chat/e50cd269c58150ca30d26740b715bbd76cebb856/django-chat-app.gif -------------------------------------------------------------------------------- /frontend/.env.development: -------------------------------------------------------------------------------- 1 | VITE_APP_API_URL=http://127.0.0.1:8000/chat-app/ 2 | VITE_WS_ENDPOINT=ws://127.0.0.1:8000/ -------------------------------------------------------------------------------- /frontend/.env.production: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmrifat/django_chat/e50cd269c58150ca30d26740b715bbd76cebb856/frontend/.env.production -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /frontend/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["johnsoncodehk.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Vite 2 | 3 | This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` 16 | 17 | 18 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.26.1", 12 | "bootstrap": "^5.1.3", 13 | "moment": "^2.29.3", 14 | "peerjs": "^1.4.4", 15 | "sass": "^1.50.0", 16 | "toastr": "^2.1.4", 17 | "vue": "^3.2.25", 18 | "vue-router": "^4.0.14", 19 | "vuex": "^4.0.2", 20 | "vuex-persistedstate": "^4.1.0" 21 | }, 22 | "devDependencies": { 23 | "@vitejs/plugin-vue": "^2.3.0", 24 | "vite": "^2.9.0", 25 | "vite-plugin-mkcert": "^1.6.4" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmrifat/django_chat/e50cd269c58150ca30d26740b715bbd76cebb856/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/sounds/iphone-ringtone-47958.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmrifat/django_chat/e50cd269c58150ca30d26740b715bbd76cebb856/frontend/public/sounds/iphone-ringtone-47958.mp3 -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/check.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/edit-3.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/girl.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/log-out.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/message-circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/more-vertical.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/phone.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/settings.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/video.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmrifat/django_chat/e50cd269c58150ca30d26740b715bbd76cebb856/frontend/src/assets/logo.png -------------------------------------------------------------------------------- /frontend/src/axios.js: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | import store from './store/index' 3 | 4 | console.log(import.meta.env) 5 | 6 | const instance = axios.create({ 7 | baseURL: import.meta.env.VITE_APP_API_URL 8 | }) 9 | 10 | instance.interceptors.request.use( 11 | (config) => { 12 | if (store.state.token) { 13 | config.headers['Authorization'] = `Bearer ${store.state.token}` 14 | } 15 | return config 16 | } 17 | ) 18 | 19 | instance.interceptors.request.use(config => { 20 | // app.$Progress.start(); 21 | return config 22 | }, (error) => { 23 | // app.$Progress.fail(); 24 | return Promise.reject(error); 25 | }) 26 | 27 | instance.interceptors.response.use(response => { 28 | // app.$Progress.finish() 29 | return response 30 | }, (error) => { 31 | // app.$Progress.fail(); 32 | return Promise.reject(error); 33 | }) 34 | 35 | export default instance; -------------------------------------------------------------------------------- /frontend/src/components/ChatApp.vue: -------------------------------------------------------------------------------- 1 | 309 | 310 | 320 | 321 | -------------------------------------------------------------------------------- /frontend/src/components/ChatWindow.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 75 | 76 | -------------------------------------------------------------------------------- /frontend/src/components/DefaultMessageWindow.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 35 | 36 | 41 | -------------------------------------------------------------------------------- /frontend/src/components/buttons/MicButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 34 | 35 | -------------------------------------------------------------------------------- /frontend/src/components/buttons/VideoButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 34 | 35 | -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import {createApp} from 'vue' 2 | import App from './App.vue' 3 | import router from "./router/index" 4 | import store from "./store"; 5 | 6 | import './styles/main.scss' 7 | 8 | createApp(App).use(store).use(router).mount('#app') 9 | -------------------------------------------------------------------------------- /frontend/src/router/index.js: -------------------------------------------------------------------------------- 1 | import {createRouter, createWebHistory} from "vue-router"; 2 | import store from "../store"; 3 | 4 | const routes = [ 5 | { 6 | path: '/', component: () => import('../views/auth/Layout.vue'), 7 | children: [ 8 | {path: '', component: () => import('../views/auth/Login.vue')}, 9 | {path: 'registration', component: () => import('../views/auth/Registration.vue')}, 10 | {path: 'forget-password', component: () => import('../views/auth/ForgetPassword.vue')}, 11 | {path: 'reset-password', component: () => import('../views/auth/ResetPassword.vue')} 12 | ], 13 | meta: { 14 | guest: true 15 | } 16 | }, 17 | { 18 | path: '/app', component: () => import('../views/app/Layout.vue'), 19 | children: [ 20 | {path: '', component: () => import('../views/app/chat-app/ChatApp.vue')}, 21 | { 22 | path: "/call-view", component: () => import('../views/app/call-app/BaseCallView.vue'), 23 | children: [ 24 | { 25 | name: 'callerView', 26 | path: 'sender/:username/:receiver', 27 | component: () => import('../views/app/call-app/Sender.vue') 28 | }, 29 | { 30 | path: 'receiver/:username/:sender', 31 | name: 'receiverView', 32 | component: () => import('../views/app/call-app/Receiver.vue') 33 | }, 34 | ] 35 | }, 36 | ], 37 | meta: { 38 | auth: true 39 | } 40 | } 41 | ] 42 | 43 | 44 | const router = createRouter({ 45 | history: createWebHistory(), 46 | routes 47 | }) 48 | 49 | router.beforeEach((to, from, next) => { 50 | window.scrollTo(0, 0) 51 | if (to.matched.some(record => record.meta.auth)) { 52 | if (store.state.token == null) { 53 | next({ 54 | path: '/', 55 | params: {nextUrl: to.fullPath} 56 | }) 57 | } else { 58 | next() 59 | } 60 | } else if (to.matched.some(record => record.meta.guest)) { 61 | if (store.state.token == null) { 62 | next() 63 | } else { 64 | next({ 65 | path: '/app', 66 | params: {nextUrl: to.fullPath} 67 | }) 68 | } 69 | } else { 70 | next() 71 | } 72 | }) 73 | 74 | export default router -------------------------------------------------------------------------------- /frontend/src/store/index.js: -------------------------------------------------------------------------------- 1 | import {createStore} from "vuex"; 2 | import createPersistedState from 'vuex-persistedstate'; 3 | import {Peer} from "peerjs"; 4 | 5 | const store = createStore({ 6 | state: { 7 | activeUser: null, 8 | token: null, 9 | peer_id: null, 10 | active_message: null 11 | }, 12 | plugins: [createPersistedState()], 13 | mutations: { 14 | UPDATE_USER(state, payload) { 15 | state.activeUser = payload 16 | }, 17 | SET_TOKEN(state, payload) { 18 | state.token = payload 19 | }, 20 | SET_PEER(state, payload) { 21 | state.peer_id = payload 22 | }, 23 | SET_ACTIVE_MESSAGE(state, payload) { 24 | state.active_message = payload 25 | } 26 | }, 27 | actions: { 28 | clearState(context) { 29 | context.commit('UPDATE_USER') 30 | context.commit('SET_TOKEN') 31 | }, 32 | generatePeerId(context) { 33 | const peer = new Peer() 34 | peer.on('open', (id) => { 35 | context.commit('SET_PEER', id) 36 | }) 37 | } 38 | } 39 | }) 40 | 41 | export default store -------------------------------------------------------------------------------- /frontend/src/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import "node_modules/bootstrap/scss/bootstrap"; 2 | 3 | html { 4 | scroll-behavior: smooth; 5 | } 6 | 7 | @import "pages/chat"; 8 | @import "pages/call"; 9 | @import "pages/login"; 10 | @import "toastr"; -------------------------------------------------------------------------------- /frontend/src/styles/pages/_call.scss: -------------------------------------------------------------------------------- 1 | .pulse { 2 | height: 270px; 3 | width: 270px; 4 | background-color: #19a299; 5 | border-radius: 50%; 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | position: relative; 10 | 11 | &:before { 12 | content: ""; 13 | position: absolute; 14 | border: 1px solid #19a299; 15 | width: calc(100% + 40px); 16 | height: calc(100% + 40px); 17 | border-radius: 50%; 18 | animation: pulse 1s linear infinite 19 | } 20 | 21 | &:after { 22 | content: ""; 23 | position: absolute; 24 | border: 1px solid #19a299; 25 | width: calc(100% + 40px); 26 | height: calc(100% + 40px); 27 | border-radius: 50%; 28 | animation: pulse 1s linear infinite; 29 | animation-delay: 0.3s 30 | } 31 | } 32 | 33 | 34 | @keyframes pulse { 35 | 0% { 36 | transform: scale(0.5); 37 | opacity: 0 38 | } 39 | 40 | 50% { 41 | transform: scale(1); 42 | opacity: 1 43 | } 44 | 45 | 100% { 46 | transform: scale(1.3); 47 | opacity: 0 48 | } 49 | } 50 | 51 | 52 | 53 | #remoteVideo { 54 | position: fixed; 55 | top: 0; 56 | left: 0; 57 | width: 100vw; 58 | height: 100vh; 59 | object-fit: contain; 60 | transform: rotateY(180deg); 61 | } 62 | 63 | #localVideo { 64 | position: fixed; 65 | transform: rotateY(180deg); 66 | right: 0; 67 | top: 0; 68 | width: 20vw; 69 | height: 20vh; 70 | object-fit: contain; 71 | z-index: 999; 72 | } 73 | 74 | .call-controls { 75 | position: fixed; 76 | bottom: 0; 77 | left: 0; 78 | width: 100%; 79 | } -------------------------------------------------------------------------------- /frontend/src/styles/pages/_chat.scss: -------------------------------------------------------------------------------- 1 | .chat-layout { 2 | display: flex; 3 | outline: none !important; 4 | 5 | ::-webkit-scrollbar { 6 | //display: none; 7 | } 8 | 9 | .chat-leftbar { 10 | width: 320px; 11 | height: 100vh; 12 | overflow: hidden; 13 | background-color: #fff; 14 | 15 | .chat-left-headbar { 16 | padding: 15px; 17 | border-bottom: 1px solid rgba(0, 0, 0, 0.03); 18 | position: sticky; 19 | top: 0; 20 | background-color: #fff; 21 | z-index: 1; 22 | 23 | a { 24 | float: right; 25 | font-size: 20px; 26 | color: #4c595f; 27 | } 28 | } 29 | 30 | .chat-left-search { 31 | padding: 15px; 32 | border-bottom: 1px solid rgba(0, 0, 0, 0.03); 33 | position: sticky; 34 | top: 0; 35 | 36 | 37 | input[type=search] { 38 | background-color: rgba(129, 167, 205, 0.1); 39 | color: #8A98AC; 40 | font-size: 16px; 41 | padding-left: 20px; 42 | border: none; 43 | border-radius: 3px 0 0 3px; 44 | } 45 | 46 | .input-group-append { 47 | margin-left: 0; 48 | } 49 | 50 | .btn { 51 | background-color: rgba(129, 167, 205, 0.1); 52 | color: #8A98AC; 53 | font-size: 18px; 54 | border-radius: 0 3px 3px 0; 55 | box-shadow: none; 56 | z-index: 0; 57 | 58 | img { 59 | height: 18px; 60 | color: #8A98AC; 61 | } 62 | } 63 | } 64 | 65 | .chat-left-body { 66 | padding-bottom: 109px; 67 | height: calc(100vh - 100px); 68 | overflow: auto; 69 | 70 | .chat-userlist { 71 | .media { 72 | display: flex; 73 | align-items: flex-start; 74 | 75 | .chat-timing { 76 | color: #8A98AC; 77 | float: right; 78 | font-size: 11px; 79 | font-weight: 400; 80 | margin-top: 3px; 81 | } 82 | 83 | .user-status { 84 | position: relative; 85 | left: 0; 86 | width: 12px; 87 | height: 12px; 88 | border-radius: 50%; 89 | border: 2px solid #fff; 90 | background-color: #19a299; 91 | 92 | ~ img { 93 | margin-left: -12px; 94 | } 95 | } 96 | 97 | img { 98 | width: 40px; 99 | margin-right: 10px; 100 | } 101 | 102 | .media-body { 103 | word-break: break-word; 104 | flex: 1; 105 | } 106 | 107 | h5 { 108 | font-size: 15px; 109 | margin-bottom: 5px; 110 | font-weight: 600; 111 | color: #263a5b; 112 | } 113 | 114 | p { 115 | margin-bottom: 0; 116 | font-size: 14px; 117 | } 118 | } 119 | } 120 | 121 | .nav-pills { 122 | .nav-link { 123 | padding: 15px !important; 124 | border-bottom: none !important; 125 | cursor: pointer; 126 | color: #8A98AC; 127 | border-radius: 0; 128 | 129 | .badge { 130 | font-weight: 600; 131 | } 132 | 133 | .badge-primary { 134 | background-color: #19a299; 135 | } 136 | 137 | &:hover { 138 | background-color: #f1f3f4; 139 | border-right: 2px solid #19a299; 140 | } 141 | 142 | &.active { 143 | background-color: #f1f3f4; 144 | border-right: 2px solid #19a299; 145 | 146 | img { 147 | fill: #f1f3f4; 148 | } 149 | } 150 | } 151 | } 152 | } 153 | 154 | .chat-menu { 155 | background-color: #fff; 156 | padding: 0 15px; 157 | border-top: 1px solid rgba(0, 0, 0, 0.03); 158 | position: fixed; 159 | width: 320px; 160 | bottom: 0; 161 | z-index: 9; 162 | 163 | .nav-pills { 164 | .nav-link { 165 | border-radius: 0; 166 | padding: 18px 0; 167 | 168 | &.active { 169 | background-color: #fff; 170 | border-bottom: 2px solid #19a299; 171 | } 172 | } 173 | } 174 | } 175 | } 176 | 177 | .chat-rightbar { 178 | flex: 1; 179 | height: 100vh; 180 | max-width: calc(100vw - 320px); 181 | overflow: hidden; 182 | 183 | .chat-details { 184 | background-color: #fff; 185 | border-left: 1px solid rgba(0, 0, 0, 0.03); 186 | border-right: 1px solid rgba(0, 0, 0, 0.03); 187 | flex: 1; 188 | 189 | .chat-head { 190 | padding: 15px; 191 | border-bottom: 1px solid rgba(0, 0, 0, 0.03); 192 | 193 | .user-status { 194 | position: relative; 195 | left: 12px; 196 | margin-left: -12px; 197 | width: 12px; 198 | height: 12px; 199 | border-radius: 50%; 200 | border: 2px solid #fff; 201 | background-color: #19a299; 202 | } 203 | 204 | img { 205 | width: 40px; 206 | margin-right: 10px; 207 | } 208 | 209 | h5 { 210 | margin-bottom: 5px; 211 | font-size: 15px; 212 | } 213 | 214 | p { 215 | font-size: 13px; 216 | line-height: 1.3; 217 | margin-bottom: 0 !important; 218 | margin-top: 0 !important; 219 | } 220 | 221 | .list-inline { 222 | li { 223 | button { 224 | outline: none; 225 | border: 0; 226 | background-color: transparent; 227 | 228 | img { 229 | height: 20px; 230 | } 231 | } 232 | } 233 | } 234 | } 235 | 236 | .chat-body { 237 | background-image: url("http://themesbox.in/admin-templates/gappa/html/light/assets/images/authentication-bg.jpg"); 238 | padding: 30px 30px 41px; 239 | height: calc(100vh - 138px); 240 | overflow: auto; 241 | 242 | .chat-message { 243 | margin-bottom: 15px; 244 | 245 | &.right { 246 | text-align: right; 247 | 248 | .text { 249 | background-color: #19a299; 250 | color: #ffffff; 251 | 252 | &:after { 253 | content: " "; 254 | position: absolute; 255 | width: 0; 256 | height: 0; 257 | left: auto; 258 | right: -10px; 259 | top: auto; 260 | bottom: 0; 261 | border: 6px solid #19a299; 262 | border-color: transparent transparent #19a299 #19a299; 263 | } 264 | 265 | } 266 | } 267 | 268 | &.left { 269 | text-align: left; 270 | 271 | .text { 272 | background-color: #ffffff; 273 | 274 | &:after { 275 | content: " "; 276 | position: absolute; 277 | width: 0; 278 | height: 0; 279 | left: -10px; 280 | right: auto; 281 | top: auto; 282 | bottom: 0; 283 | border: 6px solid #ffffff; 284 | border-color: transparent #ffffff #ffffff transparent; 285 | } 286 | } 287 | } 288 | 289 | .text { 290 | padding: 5px 10px; 291 | display: inline-block; 292 | border-radius: 3px; 293 | position: relative; 294 | } 295 | 296 | .meta { 297 | margin-top: 5px; 298 | font-size: 13px; 299 | color: #4c595f; 300 | } 301 | } 302 | } 303 | 304 | .chat-bottom { 305 | background-color: #ffffff; 306 | padding: 15px 30px; 307 | border-top: 1px solid rgba(0, 0, 0, 0.03); 308 | border-left: 1px solid rgba(0, 0, 0, 0.03); 309 | position: fixed; 310 | bottom: 0; 311 | left: 320px; 312 | right: 0; 313 | 314 | .chat-messagebar { 315 | input[type=text] { 316 | background-color: transparent; 317 | color: #8A98AC; 318 | font-size: 16px; 319 | border: none; 320 | border-radius: 0; 321 | } 322 | } 323 | } 324 | 325 | } 326 | } 327 | } 328 | 329 | li { 330 | &.media { 331 | display: flex; 332 | align-items: flex-start; 333 | 334 | .media-body { 335 | word-break: break-word; 336 | flex: 1; 337 | } 338 | } 339 | } 340 | 341 | .form-control:focus { 342 | box-shadow: none !important; 343 | } -------------------------------------------------------------------------------- /frontend/src/styles/pages/_login.scss: -------------------------------------------------------------------------------- 1 | .auth { 2 | background-image: url("http://themesbox.in/admin-templates/gappa/html/light/assets/images/authentication-bg.jpg"); 3 | height: 100vh; 4 | 5 | .vertical-center { 6 | margin-top: calc(100vh - 70vh); 7 | } 8 | } -------------------------------------------------------------------------------- /frontend/src/views/app/Layout.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /frontend/src/views/app/call-app/BaseCallView.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /frontend/src/views/app/call-app/Receiver.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 172 | 173 | -------------------------------------------------------------------------------- /frontend/src/views/app/call-app/Sender.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 198 | 199 | -------------------------------------------------------------------------------- /frontend/src/views/app/chat-app/ChatApp.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 157 | 158 | -------------------------------------------------------------------------------- /frontend/src/views/app/chat-app/particals/leftbar/Header.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 43 | 44 | -------------------------------------------------------------------------------- /frontend/src/views/app/chat-app/particals/leftbar/Search.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 23 | -------------------------------------------------------------------------------- /frontend/src/views/app/chat-app/particals/leftbar/UserList.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 58 | 59 | -------------------------------------------------------------------------------- /frontend/src/views/app/chat-app/particals/rightbar/ActiveUser.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 68 | 69 | -------------------------------------------------------------------------------- /frontend/src/views/app/chat-app/particals/rightbar/MessageWindow.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 104 | 105 | -------------------------------------------------------------------------------- /frontend/src/views/app/chat-app/store.js: -------------------------------------------------------------------------------- 1 | import {reactive} from "vue"; 2 | 3 | export const store = reactive({ 4 | users: [ 5 | { 6 | name: 'Emily Cook', 7 | photo: '/src/assets/icons/girl.svg', 8 | online: true, 9 | status: 'Hi i am using dj chat', 10 | messages: [ 11 | { 12 | text: 'This is message', 13 | read: true, 14 | date_time: 'Yesterday', 15 | sender_id: 'me' 16 | }, 17 | { 18 | text: 'This is message', 19 | read: true, 20 | date_time: 'Yesterday', 21 | sender_id: 'him' 22 | } 23 | ] 24 | } 25 | ], 26 | selected_user: null 27 | }) -------------------------------------------------------------------------------- /frontend/src/views/auth/ForgetPassword.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /frontend/src/views/auth/Layout.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 30 | 31 | -------------------------------------------------------------------------------- /frontend/src/views/auth/Login.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 64 | 65 | -------------------------------------------------------------------------------- /frontend/src/views/auth/Registration.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 77 | 78 | -------------------------------------------------------------------------------- /frontend/src/views/auth/ResetPassword.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import mkcert from 'vite-plugin-mkcert' 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | server: { 8 | https: true, 9 | host: true 10 | }, 11 | plugins: [vue(), mkcert()] 12 | }) 13 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | ssl -------------------------------------------------------------------------------- /server/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | django = "*" 8 | channels = "*" 9 | pillow = "*" 10 | djangorestframework = "*" 11 | markdown = "*" 12 | django-filter = "*" 13 | channels-redis = "*" 14 | django-cors-headers = "*" 15 | django-debug-toolbar = "*" 16 | 17 | [dev-packages] 18 | 19 | [requires] 20 | python_version = "3.10" 21 | -------------------------------------------------------------------------------- /server/Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "5b2d44c1f0df95b9e7fa56113fba6cffae70ee703214ec89635c03cb09c76b48" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.10" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "aioredis": { 20 | "hashes": [ 21 | "sha256:15f8af30b044c771aee6787e5ec24694c048184c7b9e54c3b60c750a4b93273a", 22 | "sha256:b61808d7e97b7cd5a92ed574937a079c9387fdadd22bfbfa7ad2fd319ecc26e3" 23 | ], 24 | "version": "==1.3.1" 25 | }, 26 | "asgiref": { 27 | "hashes": [ 28 | "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4", 29 | "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424" 30 | ], 31 | "markers": "python_version >= '3.7'", 32 | "version": "==3.5.2" 33 | }, 34 | "async-timeout": { 35 | "hashes": [ 36 | "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15", 37 | "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c" 38 | ], 39 | "markers": "python_version >= '3.6'", 40 | "version": "==4.0.2" 41 | }, 42 | "attrs": { 43 | "hashes": [ 44 | "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", 45 | "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" 46 | ], 47 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 48 | "version": "==21.4.0" 49 | }, 50 | "autobahn": { 51 | "hashes": [ 52 | "sha256:57b7acf228d50d83cf327372b889e2a168a869275b26e17917ed0b4cf4d823a6" 53 | ], 54 | "markers": "python_version >= '3.7'", 55 | "version": "==22.4.2" 56 | }, 57 | "automat": { 58 | "hashes": [ 59 | "sha256:7979803c74610e11ef0c0d68a2942b152df52da55336e0c9d58daf1831cbdf33", 60 | "sha256:b6feb6455337df834f6c9962d6ccf771515b7d939bca142b29c20c2376bc6111" 61 | ], 62 | "version": "==20.2.0" 63 | }, 64 | "cffi": { 65 | "hashes": [ 66 | "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3", 67 | "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2", 68 | "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636", 69 | "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20", 70 | "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728", 71 | "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27", 72 | "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66", 73 | "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443", 74 | "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0", 75 | "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7", 76 | "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39", 77 | "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605", 78 | "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a", 79 | "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37", 80 | "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029", 81 | "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139", 82 | "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc", 83 | "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df", 84 | "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14", 85 | "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880", 86 | "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2", 87 | "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a", 88 | "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e", 89 | "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474", 90 | "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024", 91 | "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8", 92 | "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0", 93 | "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e", 94 | "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a", 95 | "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e", 96 | "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032", 97 | "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6", 98 | "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e", 99 | "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b", 100 | "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e", 101 | "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954", 102 | "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962", 103 | "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c", 104 | "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4", 105 | "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55", 106 | "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962", 107 | "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023", 108 | "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c", 109 | "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6", 110 | "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8", 111 | "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382", 112 | "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7", 113 | "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc", 114 | "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997", 115 | "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796" 116 | ], 117 | "version": "==1.15.0" 118 | }, 119 | "channels": { 120 | "hashes": [ 121 | "sha256:0ff0422b4224d10efac76e451575517f155fe7c97d369b5973b116f22eeaf86c", 122 | "sha256:fdd9a94987a23d8d7ebd97498ed8b8cc83163f37e53fc6c85098aba7a3bb8b75" 123 | ], 124 | "index": "pypi", 125 | "version": "==3.0.4" 126 | }, 127 | "channels-redis": { 128 | "hashes": [ 129 | "sha256:5dffd4cc16174125bd4043fc8fe7462ca7403cf801d59a9fa7410ed101fa6a57", 130 | "sha256:6e4565b7c11c6bcde5d48556cb83bd043779697ff03811867d2f895aa6170d56" 131 | ], 132 | "index": "pypi", 133 | "version": "==3.4.0" 134 | }, 135 | "constantly": { 136 | "hashes": [ 137 | "sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35", 138 | "sha256:dd2fa9d6b1a51a83f0d7dd76293d734046aa176e384bf6e33b7e44880eb37c5d" 139 | ], 140 | "version": "==15.1.0" 141 | }, 142 | "cryptography": { 143 | "hashes": [ 144 | "sha256:093cb351031656d3ee2f4fa1be579a8c69c754cf874206be1d4cf3b542042804", 145 | "sha256:0cc20f655157d4cfc7bada909dc5cc228211b075ba8407c46467f63597c78178", 146 | "sha256:1b9362d34363f2c71b7853f6251219298124aa4cc2075ae2932e64c91a3e2717", 147 | "sha256:1f3bfbd611db5cb58ca82f3deb35e83af34bb8cf06043fa61500157d50a70982", 148 | "sha256:2bd1096476aaac820426239ab534b636c77d71af66c547b9ddcd76eb9c79e004", 149 | "sha256:31fe38d14d2e5f787e0aecef831457da6cec68e0bb09a35835b0b44ae8b988fe", 150 | "sha256:3b8398b3d0efc420e777c40c16764d6870bcef2eb383df9c6dbb9ffe12c64452", 151 | "sha256:3c81599befb4d4f3d7648ed3217e00d21a9341a9a688ecdd615ff72ffbed7336", 152 | "sha256:419c57d7b63f5ec38b1199a9521d77d7d1754eb97827bbb773162073ccd8c8d4", 153 | "sha256:46f4c544f6557a2fefa7ac8ac7d1b17bf9b647bd20b16decc8fbcab7117fbc15", 154 | "sha256:471e0d70201c069f74c837983189949aa0d24bb2d751b57e26e3761f2f782b8d", 155 | "sha256:59b281eab51e1b6b6afa525af2bd93c16d49358404f814fe2c2410058623928c", 156 | "sha256:731c8abd27693323b348518ed0e0705713a36d79fdbd969ad968fbef0979a7e0", 157 | "sha256:95e590dd70642eb2079d280420a888190aa040ad20f19ec8c6e097e38aa29e06", 158 | "sha256:a68254dd88021f24a68b613d8c51d5c5e74d735878b9e32cc0adf19d1f10aaf9", 159 | "sha256:a7d5137e556cc0ea418dca6186deabe9129cee318618eb1ffecbd35bee55ddc1", 160 | "sha256:aeaba7b5e756ea52c8861c133c596afe93dd716cbcacae23b80bc238202dc023", 161 | "sha256:dc26bb134452081859aa21d4990474ddb7e863aa39e60d1592800a8865a702de", 162 | "sha256:e53258e69874a306fcecb88b7534d61820db8a98655662a3dd2ec7f1afd9132f", 163 | "sha256:ef15c2df7656763b4ff20a9bc4381d8352e6640cfeb95c2972c38ef508e75181", 164 | "sha256:f224ad253cc9cea7568f49077007d2263efa57396a2f2f78114066fd54b5c68e", 165 | "sha256:f8ec91983e638a9bcd75b39f1396e5c0dc2330cbd9ce4accefe68717e6779e0a" 166 | ], 167 | "markers": "python_version >= '3.6'", 168 | "version": "==37.0.2" 169 | }, 170 | "daphne": { 171 | "hashes": [ 172 | "sha256:76ffae916ba3aa66b46996c14fa713e46004788167a4873d647544e750e0e99f", 173 | "sha256:a9af943c79717bc52fe64a3c236ae5d3adccc8b5be19c881b442d2c3db233393" 174 | ], 175 | "markers": "python_version >= '3.6'", 176 | "version": "==3.0.2" 177 | }, 178 | "django": { 179 | "hashes": [ 180 | "sha256:07c8638e7a7f548dc0acaaa7825d84b7bd42b10e8d22268b3d572946f1e9b687", 181 | "sha256:4e8177858524417563cc0430f29ea249946d831eacb0068a1455686587df40b5" 182 | ], 183 | "index": "pypi", 184 | "version": "==4.0.4" 185 | }, 186 | "django-cors-headers": { 187 | "hashes": [ 188 | "sha256:39d1d5acb872c1860ecfd88b8572bfbb3a1f201b5685ede951d71fc57c7dfae5", 189 | "sha256:5f07e2ff8a95c887698e748588a4a0b2ad0ad1b5a292e2d33132f1253e2a97cb" 190 | ], 191 | "index": "pypi", 192 | "version": "==3.12.0" 193 | }, 194 | "django-debug-toolbar": { 195 | "hashes": [ 196 | "sha256:42c1c2e9dc05bb57b53d641e3a6d131fc031b92377b34ae32e604a1fe439bb83", 197 | "sha256:ae6bec2c1ce0e6900b0ab0443e1427eb233d8e6f57a84a0b2705eeecb8874e22" 198 | ], 199 | "index": "pypi", 200 | "version": "==3.4.0" 201 | }, 202 | "django-filter": { 203 | "hashes": [ 204 | "sha256:632a251fa8f1aadb4b8cceff932bb52fe2f826dd7dfe7f3eac40e5c463d6836e", 205 | "sha256:f4a6737a30104c98d2e2a5fb93043f36dd7978e0c7ddc92f5998e85433ea5063" 206 | ], 207 | "index": "pypi", 208 | "version": "==21.1" 209 | }, 210 | "djangorestframework": { 211 | "hashes": [ 212 | "sha256:0c33407ce23acc68eca2a6e46424b008c9c02eceb8cf18581921d0092bc1f2ee", 213 | "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa" 214 | ], 215 | "index": "pypi", 216 | "version": "==3.13.1" 217 | }, 218 | "hiredis": { 219 | "hashes": [ 220 | "sha256:04026461eae67fdefa1949b7332e488224eac9e8f2b5c58c98b54d29af22093e", 221 | "sha256:04927a4c651a0e9ec11c68e4427d917e44ff101f761cd3b5bc76f86aaa431d27", 222 | "sha256:07bbf9bdcb82239f319b1f09e8ef4bdfaec50ed7d7ea51a56438f39193271163", 223 | "sha256:09004096e953d7ebd508cded79f6b21e05dff5d7361771f59269425108e703bc", 224 | "sha256:0adea425b764a08270820531ec2218d0508f8ae15a448568109ffcae050fee26", 225 | "sha256:0b39ec237459922c6544d071cdcf92cbb5bc6685a30e7c6d985d8a3e3a75326e", 226 | "sha256:0d5109337e1db373a892fdcf78eb145ffb6bbd66bb51989ec36117b9f7f9b579", 227 | "sha256:0f41827028901814c709e744060843c77e78a3aca1e0d6875d2562372fcb405a", 228 | "sha256:11d119507bb54e81f375e638225a2c057dda748f2b1deef05c2b1a5d42686048", 229 | "sha256:1233e303645f468e399ec906b6b48ab7cd8391aae2d08daadbb5cad6ace4bd87", 230 | "sha256:139705ce59d94eef2ceae9fd2ad58710b02aee91e7fa0ccb485665ca0ecbec63", 231 | "sha256:1f03d4dadd595f7a69a75709bc81902673fa31964c75f93af74feac2f134cc54", 232 | "sha256:240ce6dc19835971f38caf94b5738092cb1e641f8150a9ef9251b7825506cb05", 233 | "sha256:294a6697dfa41a8cba4c365dd3715abc54d29a86a40ec6405d677ca853307cfb", 234 | "sha256:3d55e36715ff06cdc0ab62f9591607c4324297b6b6ce5b58cb9928b3defe30ea", 235 | "sha256:3dddf681284fe16d047d3ad37415b2e9ccdc6c8986c8062dbe51ab9a358b50a5", 236 | "sha256:3f5f7e3a4ab824e3de1e1700f05ad76ee465f5f11f5db61c4b297ec29e692b2e", 237 | "sha256:508999bec4422e646b05c95c598b64bdbef1edf0d2b715450a078ba21b385bcc", 238 | "sha256:5d2a48c80cf5a338d58aae3c16872f4d452345e18350143b3bf7216d33ba7b99", 239 | "sha256:5dc7a94bb11096bc4bffd41a3c4f2b958257085c01522aa81140c68b8bf1630a", 240 | "sha256:65d653df249a2f95673976e4e9dd7ce10de61cfc6e64fa7eeaa6891a9559c581", 241 | "sha256:7492af15f71f75ee93d2a618ca53fea8be85e7b625e323315169977fae752426", 242 | "sha256:7f0055f1809b911ab347a25d786deff5e10e9cf083c3c3fd2dd04e8612e8d9db", 243 | "sha256:807b3096205c7cec861c8803a6738e33ed86c9aae76cac0e19454245a6bbbc0a", 244 | "sha256:81d6d8e39695f2c37954d1011c0480ef7cf444d4e3ae24bc5e89ee5de360139a", 245 | "sha256:87c7c10d186f1743a8fd6a971ab6525d60abd5d5d200f31e073cd5e94d7e7a9d", 246 | "sha256:8b42c0dc927b8d7c0eb59f97e6e34408e53bc489f9f90e66e568f329bff3e443", 247 | "sha256:a00514362df15af041cc06e97aebabf2895e0a7c42c83c21894be12b84402d79", 248 | "sha256:a39efc3ade8c1fb27c097fd112baf09d7fd70b8cb10ef1de4da6efbe066d381d", 249 | "sha256:a4ee8000454ad4486fb9f28b0cab7fa1cd796fc36d639882d0b34109b5b3aec9", 250 | "sha256:a7928283143a401e72a4fad43ecc85b35c27ae699cf5d54d39e1e72d97460e1d", 251 | "sha256:adf4dd19d8875ac147bf926c727215a0faf21490b22c053db464e0bf0deb0485", 252 | "sha256:ae8427a5e9062ba66fc2c62fb19a72276cf12c780e8db2b0956ea909c48acff5", 253 | "sha256:b4c8b0bc5841e578d5fb32a16e0c305359b987b850a06964bd5a62739d688048", 254 | "sha256:b84f29971f0ad4adaee391c6364e6f780d5aae7e9226d41964b26b49376071d0", 255 | "sha256:c39c46d9e44447181cd502a35aad2bb178dbf1b1f86cf4db639d7b9614f837c6", 256 | "sha256:cb2126603091902767d96bcb74093bd8b14982f41809f85c9b96e519c7e1dc41", 257 | "sha256:dcef843f8de4e2ff5e35e96ec2a4abbdf403bd0f732ead127bd27e51f38ac298", 258 | "sha256:e3447d9e074abf0e3cd85aef8131e01ab93f9f0e86654db7ac8a3f73c63706ce", 259 | "sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0", 260 | "sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a" 261 | ], 262 | "markers": "python_version >= '3.6'", 263 | "version": "==2.0.0" 264 | }, 265 | "hyperlink": { 266 | "hashes": [ 267 | "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b", 268 | "sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4" 269 | ], 270 | "version": "==21.0.0" 271 | }, 272 | "idna": { 273 | "hashes": [ 274 | "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", 275 | "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" 276 | ], 277 | "version": "==3.3" 278 | }, 279 | "incremental": { 280 | "hashes": [ 281 | "sha256:02f5de5aff48f6b9f665d99d48bfc7ec03b6e3943210de7cfc88856d755d6f57", 282 | "sha256:92014aebc6a20b78a8084cdd5645eeaa7f74b8933f70fa3ada2cfbd1e3b54321" 283 | ], 284 | "version": "==21.3.0" 285 | }, 286 | "markdown": { 287 | "hashes": [ 288 | "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874", 289 | "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621" 290 | ], 291 | "index": "pypi", 292 | "version": "==3.3.7" 293 | }, 294 | "msgpack": { 295 | "hashes": [ 296 | "sha256:0d8c332f53ffff01953ad25131272506500b14750c1d0ce8614b17d098252fbc", 297 | "sha256:1c58cdec1cb5fcea8c2f1771d7b5fec79307d056874f746690bd2bdd609ab147", 298 | "sha256:2c3ca57c96c8e69c1a0d2926a6acf2d9a522b41dc4253a8945c4c6cd4981a4e3", 299 | "sha256:2f30dd0dc4dfe6231ad253b6f9f7128ac3202ae49edd3f10d311adc358772dba", 300 | "sha256:2f97c0f35b3b096a330bb4a1a9247d0bd7e1f3a2eba7ab69795501504b1c2c39", 301 | "sha256:36a64a10b16c2ab31dcd5f32d9787ed41fe68ab23dd66957ca2826c7f10d0b85", 302 | "sha256:3d875631ecab42f65f9dce6f55ce6d736696ced240f2634633188de2f5f21af9", 303 | "sha256:40fb89b4625d12d6027a19f4df18a4de5c64f6f3314325049f219683e07e678a", 304 | "sha256:47d733a15ade190540c703de209ffbc42a3367600421b62ac0c09fde594da6ec", 305 | "sha256:494471d65b25a8751d19c83f1a482fd411d7ca7a3b9e17d25980a74075ba0e88", 306 | "sha256:51fdc7fb93615286428ee7758cecc2f374d5ff363bdd884c7ea622a7a327a81e", 307 | "sha256:6eef0cf8db3857b2b556213d97dd82de76e28a6524853a9beb3264983391dc1a", 308 | "sha256:6f4c22717c74d44bcd7af353024ce71c6b55346dad5e2cc1ddc17ce8c4507c6b", 309 | "sha256:73a80bd6eb6bcb338c1ec0da273f87420829c266379c8c82fa14c23fb586cfa1", 310 | "sha256:89908aea5f46ee1474cc37fbc146677f8529ac99201bc2faf4ef8edc023c2bf3", 311 | "sha256:8a3a5c4b16e9d0edb823fe54b59b5660cc8d4782d7bf2c214cb4b91a1940a8ef", 312 | "sha256:96acc674bb9c9be63fa8b6dabc3248fdc575c4adc005c440ad02f87ca7edd079", 313 | "sha256:973ad69fd7e31159eae8f580f3f707b718b61141838321c6fa4d891c4a2cca52", 314 | "sha256:9b6f2d714c506e79cbead331de9aae6837c8dd36190d02da74cb409b36162e8a", 315 | "sha256:9c0903bd93cbd34653dd63bbfcb99d7539c372795201f39d16fdfde4418de43a", 316 | "sha256:9fce00156e79af37bb6db4e7587b30d11e7ac6a02cb5bac387f023808cd7d7f4", 317 | "sha256:a598d0685e4ae07a0672b59792d2cc767d09d7a7f39fd9bd37ff84e060b1a996", 318 | "sha256:b0a792c091bac433dfe0a70ac17fc2087d4595ab835b47b89defc8bbabcf5c73", 319 | "sha256:bb87f23ae7d14b7b3c21009c4b1705ec107cb21ee71975992f6aca571fb4a42a", 320 | "sha256:bf1e6bfed4860d72106f4e0a1ab519546982b45689937b40257cfd820650b920", 321 | "sha256:c1ba333b4024c17c7591f0f372e2daa3c31db495a9b2af3cf664aef3c14354f7", 322 | "sha256:c2140cf7a3ec475ef0938edb6eb363fa704159e0bf71dde15d953bacc1cf9d7d", 323 | "sha256:c7e03b06f2982aa98d4ddd082a210c3db200471da523f9ac197f2828e80e7770", 324 | "sha256:d02cea2252abc3756b2ac31f781f7a98e89ff9759b2e7450a1c7a0d13302ff50", 325 | "sha256:da24375ab4c50e5b7486c115a3198d207954fe10aaa5708f7b65105df09109b2", 326 | "sha256:e4c309a68cb5d6bbd0c50d5c71a25ae81f268c2dc675c6f4ea8ab2feec2ac4e2", 327 | "sha256:f01b26c2290cbd74316990ba84a14ac3d599af9cebefc543d241a66e785cf17d", 328 | "sha256:f201d34dc89342fabb2a10ed7c9a9aaaed9b7af0f16a5923f1ae562b31258dea", 329 | "sha256:f74da1e5fcf20ade12c6bf1baa17a2dc3604958922de8dc83cbe3eff22e8b611" 330 | ], 331 | "version": "==1.0.3" 332 | }, 333 | "pillow": { 334 | "hashes": [ 335 | "sha256:088df396b047477dd1bbc7de6e22f58400dae2f21310d9e2ec2933b2ef7dfa4f", 336 | "sha256:09e67ef6e430f90caa093528bd758b0616f8165e57ed8d8ce014ae32df6a831d", 337 | "sha256:0b4d5ad2cd3a1f0d1df882d926b37dbb2ab6c823ae21d041b46910c8f8cd844b", 338 | "sha256:0b525a356680022b0af53385944026d3486fc8c013638cf9900eb87c866afb4c", 339 | "sha256:1d4331aeb12f6b3791911a6da82de72257a99ad99726ed6b63f481c0184b6fb9", 340 | "sha256:20d514c989fa28e73a5adbddd7a171afa5824710d0ab06d4e1234195d2a2e546", 341 | "sha256:2b291cab8a888658d72b575a03e340509b6b050b62db1f5539dd5cd18fd50578", 342 | "sha256:3f6c1716c473ebd1649663bf3b42702d0d53e27af8b64642be0dd3598c761fb1", 343 | "sha256:42dfefbef90eb67c10c45a73a9bc1599d4dac920f7dfcbf4ec6b80cb620757fe", 344 | "sha256:488f3383cf5159907d48d32957ac6f9ea85ccdcc296c14eca1a4e396ecc32098", 345 | "sha256:4d45dbe4b21a9679c3e8b3f7f4f42a45a7d3ddff8a4a16109dff0e1da30a35b2", 346 | "sha256:53c27bd452e0f1bc4bfed07ceb235663a1df7c74df08e37fd6b03eb89454946a", 347 | "sha256:55e74faf8359ddda43fee01bffbc5bd99d96ea508d8a08c527099e84eb708f45", 348 | "sha256:59789a7d06c742e9d13b883d5e3569188c16acb02eeed2510fd3bfdbc1bd1530", 349 | "sha256:5b650dbbc0969a4e226d98a0b440c2f07a850896aed9266b6fedc0f7e7834108", 350 | "sha256:66daa16952d5bf0c9d5389c5e9df562922a59bd16d77e2a276e575d32e38afd1", 351 | "sha256:6e760cf01259a1c0a50f3c845f9cad1af30577fd8b670339b1659c6d0e7a41dd", 352 | "sha256:7502539939b53d7565f3d11d87c78e7ec900d3c72945d4ee0e2f250d598309a0", 353 | "sha256:769a7f131a2f43752455cc72f9f7a093c3ff3856bf976c5fb53a59d0ccc704f6", 354 | "sha256:7c150dbbb4a94ea4825d1e5f2c5501af7141ea95825fadd7829f9b11c97aaf6c", 355 | "sha256:8844217cdf66eabe39567118f229e275f0727e9195635a15e0e4b9227458daaf", 356 | "sha256:8a66fe50386162df2da701b3722781cbe90ce043e7d53c1fd6bd801bca6b48d4", 357 | "sha256:9370d6744d379f2de5d7fa95cdbd3a4d92f0b0ef29609b4b1687f16bc197063d", 358 | "sha256:937a54e5694684f74dcbf6e24cc453bfc5b33940216ddd8f4cd8f0f79167f765", 359 | "sha256:9c857532c719fb30fafabd2371ce9b7031812ff3889d75273827633bca0c4602", 360 | "sha256:a4165205a13b16a29e1ac57efeee6be2dfd5b5408122d59ef2145bc3239fa340", 361 | "sha256:b3fe2ff1e1715d4475d7e2c3e8dabd7c025f4410f79513b4ff2de3d51ce0fa9c", 362 | "sha256:b6617221ff08fbd3b7a811950b5c3f9367f6e941b86259843eab77c8e3d2b56b", 363 | "sha256:b761727ed7d593e49671d1827044b942dd2f4caae6e51bab144d4accf8244a84", 364 | "sha256:baf3be0b9446a4083cc0c5bb9f9c964034be5374b5bc09757be89f5d2fa247b8", 365 | "sha256:c17770a62a71718a74b7548098a74cd6880be16bcfff5f937f900ead90ca8e92", 366 | "sha256:c67db410508b9de9c4694c57ed754b65a460e4812126e87f5052ecf23a011a54", 367 | "sha256:d78ca526a559fb84faaaf84da2dd4addef5edb109db8b81677c0bb1aad342601", 368 | "sha256:e9ed59d1b6ee837f4515b9584f3d26cf0388b742a11ecdae0d9237a94505d03a", 369 | "sha256:f054b020c4d7e9786ae0404278ea318768eb123403b18453e28e47cdb7a0a4bf", 370 | "sha256:f372d0f08eff1475ef426344efe42493f71f377ec52237bf153c5713de987251", 371 | "sha256:f3f6a6034140e9e17e9abc175fc7a266a6e63652028e157750bd98e804a8ed9a", 372 | "sha256:ffde4c6fabb52891d81606411cbfaf77756e3b561b566efd270b3ed3791fde4e" 373 | ], 374 | "index": "pypi", 375 | "version": "==9.1.1" 376 | }, 377 | "pyasn1": { 378 | "hashes": [ 379 | "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", 380 | "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576", 381 | "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", 382 | "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", 383 | "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", 384 | "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", 385 | "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", 386 | "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86", 387 | "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12", 388 | "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776", 389 | "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", 390 | "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2", 391 | "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3" 392 | ], 393 | "version": "==0.4.8" 394 | }, 395 | "pyasn1-modules": { 396 | "hashes": [ 397 | "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8", 398 | "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199", 399 | "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811", 400 | "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed", 401 | "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4", 402 | "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e", 403 | "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74", 404 | "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb", 405 | "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45", 406 | "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd", 407 | "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0", 408 | "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d", 409 | "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405" 410 | ], 411 | "version": "==0.2.8" 412 | }, 413 | "pycparser": { 414 | "hashes": [ 415 | "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", 416 | "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" 417 | ], 418 | "version": "==2.21" 419 | }, 420 | "pyopenssl": { 421 | "hashes": [ 422 | "sha256:660b1b1425aac4a1bea1d94168a85d99f0b3144c869dd4390d27629d0087f1bf", 423 | "sha256:ea252b38c87425b64116f808355e8da644ef9b07e429398bfece610f893ee2e0" 424 | ], 425 | "version": "==22.0.0" 426 | }, 427 | "pytz": { 428 | "hashes": [ 429 | "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7", 430 | "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c" 431 | ], 432 | "version": "==2022.1" 433 | }, 434 | "service-identity": { 435 | "hashes": [ 436 | "sha256:6e6c6086ca271dc11b033d17c3a8bea9f24ebff920c587da090afc9519419d34", 437 | "sha256:f0b0caac3d40627c3c04d7a51b6e06721857a0e10a8775f2d1d7e72901b3a7db" 438 | ], 439 | "version": "==21.1.0" 440 | }, 441 | "setuptools": { 442 | "hashes": [ 443 | "sha256:68e45d17c9281ba25dc0104eadd2647172b3472d9e01f911efa57965e8d51a36", 444 | "sha256:a43bdedf853c670e5fed28e5623403bad2f73cf02f9a2774e91def6bda8265a7" 445 | ], 446 | "markers": "python_version >= '3.7'", 447 | "version": "==62.3.2" 448 | }, 449 | "six": { 450 | "hashes": [ 451 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", 452 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" 453 | ], 454 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 455 | "version": "==1.16.0" 456 | }, 457 | "sqlparse": { 458 | "hashes": [ 459 | "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae", 460 | "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d" 461 | ], 462 | "markers": "python_version >= '3.5'", 463 | "version": "==0.4.2" 464 | }, 465 | "twisted": { 466 | "extras": [ 467 | "tls" 468 | ], 469 | "hashes": [ 470 | "sha256:a047990f57dfae1e0bd2b7df2526d4f16dcdc843774dc108b78c52f2a5f13680", 471 | "sha256:f9f7a91f94932477a9fc3b169d57f54f96c6e74a23d78d9ce54039a7f48928a2" 472 | ], 473 | "markers": "python_full_version >= '3.6.7'", 474 | "version": "==22.4.0" 475 | }, 476 | "txaio": { 477 | "hashes": [ 478 | "sha256:2e4582b70f04b2345908254684a984206c0d9b50e3074a24a4c55aba21d24d01", 479 | "sha256:41223af4a9d5726e645a8ee82480f413e5e300dd257db94bc38ae12ea48fb2e5" 480 | ], 481 | "markers": "python_version >= '3.6'", 482 | "version": "==22.2.1" 483 | }, 484 | "typing-extensions": { 485 | "hashes": [ 486 | "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708", 487 | "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376" 488 | ], 489 | "markers": "python_version >= '3.7'", 490 | "version": "==4.2.0" 491 | }, 492 | "zope.interface": { 493 | "hashes": [ 494 | "sha256:08f9636e99a9d5410181ba0729e0408d3d8748026ea938f3b970a0249daa8192", 495 | "sha256:0b465ae0962d49c68aa9733ba92a001b2a0933c317780435f00be7ecb959c702", 496 | "sha256:0cba8477e300d64a11a9789ed40ee8932b59f9ee05f85276dbb4b59acee5dd09", 497 | "sha256:0cee5187b60ed26d56eb2960136288ce91bcf61e2a9405660d271d1f122a69a4", 498 | "sha256:0ea1d73b7c9dcbc5080bb8aaffb776f1c68e807767069b9ccdd06f27a161914a", 499 | "sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3", 500 | "sha256:15e7d1f7a6ee16572e21e3576d2012b2778cbacf75eb4b7400be37455f5ca8bf", 501 | "sha256:17776ecd3a1fdd2b2cd5373e5ef8b307162f581c693575ec62e7c5399d80794c", 502 | "sha256:194d0bcb1374ac3e1e023961610dc8f2c78a0f5f634d0c737691e215569e640d", 503 | "sha256:1c0e316c9add0db48a5b703833881351444398b04111188069a26a61cfb4df78", 504 | "sha256:205e40ccde0f37496904572035deea747390a8b7dc65146d30b96e2dd1359a83", 505 | "sha256:273f158fabc5ea33cbc936da0ab3d4ba80ede5351babc4f577d768e057651531", 506 | "sha256:2876246527c91e101184f63ccd1d716ec9c46519cc5f3d5375a3351c46467c46", 507 | "sha256:2c98384b254b37ce50eddd55db8d381a5c53b4c10ee66e1e7fe749824f894021", 508 | "sha256:2e5a26f16503be6c826abca904e45f1a44ff275fdb7e9d1b75c10671c26f8b94", 509 | "sha256:334701327f37c47fa628fc8b8d28c7d7730ce7daaf4bda1efb741679c2b087fc", 510 | "sha256:3748fac0d0f6a304e674955ab1365d515993b3a0a865e16a11ec9d86fb307f63", 511 | "sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54", 512 | "sha256:3dd4952748521205697bc2802e4afac5ed4b02909bb799ba1fe239f77fd4e117", 513 | "sha256:3f24df7124c323fceb53ff6168da70dbfbae1442b4f3da439cd441681f54fe25", 514 | "sha256:469e2407e0fe9880ac690a3666f03eb4c3c444411a5a5fddfdabc5d184a79f05", 515 | "sha256:4de4bc9b6d35c5af65b454d3e9bc98c50eb3960d5a3762c9438df57427134b8e", 516 | "sha256:5208ebd5152e040640518a77827bdfcc73773a15a33d6644015b763b9c9febc1", 517 | "sha256:52de7fc6c21b419078008f697fd4103dbc763288b1406b4562554bd47514c004", 518 | "sha256:5bb3489b4558e49ad2c5118137cfeaf59434f9737fa9c5deefc72d22c23822e2", 519 | "sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e", 520 | "sha256:5dd9ca406499444f4c8299f803d4a14edf7890ecc595c8b1c7115c2342cadc5f", 521 | "sha256:5f931a1c21dfa7a9c573ec1f50a31135ccce84e32507c54e1ea404894c5eb96f", 522 | "sha256:63b82bb63de7c821428d513607e84c6d97d58afd1fe2eb645030bdc185440120", 523 | "sha256:66c0061c91b3b9cf542131148ef7ecbecb2690d48d1612ec386de9d36766058f", 524 | "sha256:6f0c02cbb9691b7c91d5009108f975f8ffeab5dff8f26d62e21c493060eff2a1", 525 | "sha256:71aace0c42d53abe6fc7f726c5d3b60d90f3c5c055a447950ad6ea9cec2e37d9", 526 | "sha256:7d97a4306898b05404a0dcdc32d9709b7d8832c0c542b861d9a826301719794e", 527 | "sha256:7df1e1c05304f26faa49fa752a8c690126cf98b40b91d54e6e9cc3b7d6ffe8b7", 528 | "sha256:8270252effc60b9642b423189a2fe90eb6b59e87cbee54549db3f5562ff8d1b8", 529 | "sha256:867a5ad16892bf20e6c4ea2aab1971f45645ff3102ad29bd84c86027fa99997b", 530 | "sha256:877473e675fdcc113c138813a5dd440da0769a2d81f4d86614e5d62b69497155", 531 | "sha256:8892f89999ffd992208754851e5a052f6b5db70a1e3f7d54b17c5211e37a98c7", 532 | "sha256:9a9845c4c6bb56e508651f005c4aeb0404e518c6f000d5a1123ab077ab769f5c", 533 | "sha256:a1e6e96217a0f72e2b8629e271e1b280c6fa3fe6e59fa8f6701bec14e3354325", 534 | "sha256:a8156e6a7f5e2a0ff0c5b21d6bcb45145efece1909efcbbbf48c56f8da68221d", 535 | "sha256:a9506a7e80bcf6eacfff7f804c0ad5350c8c95b9010e4356a4b36f5322f09abb", 536 | "sha256:af310ec8335016b5e52cae60cda4a4f2a60a788cbb949a4fbea13d441aa5a09e", 537 | "sha256:b0297b1e05fd128d26cc2460c810d42e205d16d76799526dfa8c8ccd50e74959", 538 | "sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7", 539 | "sha256:d0c1bc2fa9a7285719e5678584f6b92572a5b639d0e471bb8d4b650a1a910920", 540 | "sha256:d4d9d6c1a455d4babd320203b918ccc7fcbefe308615c521062bc2ba1aa4d26e", 541 | "sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48", 542 | "sha256:dd93ea5c0c7f3e25335ab7d22a507b1dc43976e1345508f845efc573d3d779d8", 543 | "sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4", 544 | "sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263" 545 | ], 546 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 547 | "version": "==5.4.0" 548 | } 549 | }, 550 | "develop": {} 551 | } 552 | -------------------------------------------------------------------------------- /server/chat_app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmrifat/django_chat/e50cd269c58150ca30d26740b715bbd76cebb856/server/chat_app/__init__.py -------------------------------------------------------------------------------- /server/chat_app/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | from chat_app.models import Profile 5 | 6 | 7 | @admin.register(Profile) 8 | class Profile(admin.ModelAdmin): 9 | list_display = ('user', 'photo', 'status', 'online') 10 | -------------------------------------------------------------------------------- /server/chat_app/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AuthConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'chat_app' 7 | -------------------------------------------------------------------------------- /server/chat_app/authentication.py: -------------------------------------------------------------------------------- 1 | from rest_framework.authentication import TokenAuthentication 2 | 3 | 4 | class BearerAuthentication(TokenAuthentication): 5 | keyword = 'Bearer' 6 | -------------------------------------------------------------------------------- /server/chat_app/consumers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmrifat/django_chat/e50cd269c58150ca30d26740b715bbd76cebb856/server/chat_app/consumers/__init__.py -------------------------------------------------------------------------------- /server/chat_app/consumers/message.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from asgiref.sync import async_to_sync 4 | from channels.generic.websocket import WebsocketConsumer 5 | 6 | 7 | class MessageConsumer(WebsocketConsumer): 8 | 9 | def connect(self): 10 | self.room_name = self.scope['url_route']['kwargs']['username'] 11 | self.room_group_name = 'chat_%s' % self.room_name 12 | print(self.room_name, self.room_group_name) 13 | 14 | async_to_sync(self.channel_layer.group_add)( 15 | self.room_group_name, 16 | self.channel_name 17 | ) 18 | self.accept() 19 | 20 | def receive(self, text_data): 21 | async_to_sync(self.channel_layer.group_send)( 22 | self.room_group_name, { 23 | 'type': 'new_message', 24 | 'message': json.loads(text_data)['message'] 25 | } 26 | ) 27 | 28 | def new_message(self, event): 29 | message = event['message'] 30 | self.send(text_data=json.dumps({ 31 | 'message': message, 32 | 'status': 'new_message' 33 | })) 34 | 35 | def new_call(self, event): 36 | message = event['message'] 37 | self.send(text_data=json.dumps({ 38 | 'message': message, 39 | 'status': 'new_call' 40 | })) 41 | 42 | def end_call(self, event): 43 | message = event['message'] 44 | self.send(text_data=json.dumps({ 45 | 'message': message, 46 | 'status': 'end_call' 47 | })) 48 | 49 | def disconnect(self, code): 50 | async_to_sync(self.channel_layer.group_discard)( 51 | self.room_group_name, 52 | self.channel_name 53 | ) 54 | -------------------------------------------------------------------------------- /server/chat_app/consumers/notification.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from asgiref.sync import async_to_sync 4 | from channels.generic.websocket import WebsocketConsumer, AsyncWebsocketConsumer 5 | 6 | 7 | class NewUserConsumer(WebsocketConsumer): 8 | 9 | def connect(self): 10 | self.room_name = 'new_user' 11 | self.room_group_name = 'notification' 12 | 13 | async_to_sync(self.channel_layer.group_add)( 14 | self.room_group_name, 15 | self.channel_name 16 | ) 17 | self.accept() 18 | 19 | def receive(self, text_data): 20 | async_to_sync(self.channel_layer.group_send)( 21 | self.room_group_name, { 22 | 'type': 'new_user_notification', 23 | 'message': json.loads(text_data)['message'] 24 | } 25 | ) 26 | 27 | def new_user_notification(self, event): 28 | message = event['message'] 29 | self.send(text_data=json.dumps({ 30 | 'message': message, 31 | 'status': 'new_user' 32 | })) 33 | 34 | def user_online(self, event): 35 | message = event['message'] 36 | self.send(text_data=json.dumps({ 37 | 'message': message, 38 | 'status': 'status_change' 39 | })) 40 | 41 | def disconnect(self, code): 42 | async_to_sync(self.channel_layer.group_discard)( 43 | self.room_group_name, 44 | self.channel_name 45 | ) 46 | -------------------------------------------------------------------------------- /server/chat_app/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-17 11:55 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Profile', 19 | fields=[ 20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('photo', models.ImageField(blank=True, default='media/girl.svg', null=True, upload_to='')), 22 | ('status', models.CharField(default="Hi i'm using dj chat", max_length=255)), 23 | ('online', models.BooleanField(default=False)), 24 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)), 25 | ], 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /server/chat_app/migrations/0002_alter_profile_photo_message.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-17 22:07 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('chat_app', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='profile', 18 | name='photo', 19 | field=models.ImageField(blank=True, default='girl.svg', null=True, upload_to=''), 20 | ), 21 | migrations.CreateModel( 22 | name='Message', 23 | fields=[ 24 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 25 | ('text', models.TextField()), 26 | ('date_time', models.DateTimeField(auto_now_add=True)), 27 | ('receiver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='receiver', to=settings.AUTH_USER_MODEL)), 28 | ('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sender', to=settings.AUTH_USER_MODEL)), 29 | ], 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /server/chat_app/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmrifat/django_chat/e50cd269c58150ca30d26740b715bbd76cebb856/server/chat_app/migrations/__init__.py -------------------------------------------------------------------------------- /server/chat_app/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.db import models 3 | 4 | # Create your models here. 5 | from django.db.models.signals import post_save 6 | from django.dispatch import receiver 7 | 8 | 9 | class Profile(models.Model): 10 | user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE) 11 | photo = models.ImageField(null=True, blank=True, default='girl.svg') 12 | status = models.CharField(default="Hi i'm using dj chat", max_length=255) 13 | online = models.BooleanField(default=False) 14 | 15 | 16 | @receiver(post_save, sender=User) 17 | def create_user_profile(sender, instance, created, *args, **kwargs): 18 | if created: 19 | Profile.objects.create(user_id=instance.pk) 20 | 21 | 22 | class Message(models.Model): 23 | text = models.TextField() 24 | date_time = models.DateTimeField(auto_now_add=True, blank=True) 25 | sender = models.ForeignKey(User, related_name='sender', on_delete=models.CASCADE) 26 | receiver = models.ForeignKey(User, related_name='receiver', on_delete=models.CASCADE) 27 | -------------------------------------------------------------------------------- /server/chat_app/serializers.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from asgiref.sync import async_to_sync 4 | from channels.layers import get_channel_layer 5 | from django.contrib.auth.hashers import make_password 6 | from django.contrib.auth.models import User 7 | from django.db.models import Q 8 | from rest_framework import serializers 9 | 10 | from chat_app.models import Message 11 | 12 | 13 | class MessageSerializer(serializers.Serializer): 14 | text = serializers.CharField() 15 | read = serializers.BooleanField(read_only=True) 16 | date_time = serializers.DateTimeField(required=False) 17 | sender_id = serializers.IntegerField(read_only=True) 18 | receiver = serializers.SlugField(write_only=True) 19 | 20 | def create(self, validated_data): 21 | try: 22 | user = User.objects.get(username=validated_data['receiver']) 23 | message = Message() 24 | message.text = validated_data['text'] 25 | message.sender = self.context['request'].user 26 | message.receiver = user 27 | message.save() 28 | self.__broadcast(message) 29 | return validated_data 30 | except Exception as e: 31 | raise Exception('Error', e) 32 | 33 | def __broadcast(self, message: Message): 34 | serializer = MessageModelSerializer(message, many=False) 35 | n_message = serializer.data 36 | n_message['read'] = False 37 | print(n_message) 38 | channel_layer = get_channel_layer() 39 | async_to_sync(channel_layer.group_send)( 40 | 'chat_%s' % message.receiver.username, { 41 | 'type': 'new_message', 42 | 'message': n_message 43 | } 44 | ) 45 | 46 | 47 | class MessageModelSerializer(serializers.ModelSerializer): 48 | sender = serializers.CharField(read_only=True, source='sender.username') 49 | read = serializers.BooleanField(default=True) 50 | 51 | class Meta: 52 | model = Message 53 | fields = ('text', 'sender', 'date_time', 'read') 54 | 55 | 56 | class UsersWithMessageSerializer(serializers.ModelSerializer): 57 | name = serializers.SerializerMethodField() 58 | photo = serializers.ImageField(source='profile.photo') 59 | online = serializers.BooleanField(source='profile.online') 60 | status = serializers.CharField(source='profile.status') 61 | messages = serializers.SerializerMethodField() 62 | 63 | class Meta: 64 | model = User 65 | fields = ('name', 'username', 'photo', 'online', 'status', 'messages') 66 | 67 | def get_name(self, obj): 68 | if obj.first_name: 69 | return obj.get_full_name() 70 | return obj.username 71 | 72 | def get_messages(self, obj): 73 | messages = Message.objects.filter( 74 | Q(receiver=obj, sender=self.context['request'].user) | 75 | Q(sender=obj, receiver=self.context['request'].user)).prefetch_related('sender', 'receiver') 76 | serializer = MessageModelSerializer(messages.order_by('date_time'), many=True) 77 | return serializer.data 78 | 79 | 80 | class UserSerializer(serializers.ModelSerializer): 81 | name = serializers.SerializerMethodField() 82 | photo = serializers.ImageField(source='profile.photo') 83 | online = serializers.BooleanField(source='profile.online') 84 | status = serializers.CharField(source='profile.status') 85 | messages = serializers.SerializerMethodField() 86 | 87 | class Meta: 88 | model = User 89 | fields = ('name', 'username', 'photo', 'online', 'status', 'messages') 90 | 91 | def get_name(self, obj): 92 | if obj.first_name: 93 | return obj.get_full_name() 94 | return obj.username 95 | 96 | def get_messages(self, obj): 97 | return [] 98 | 99 | 100 | class RegistrationSerializer(serializers.ModelSerializer): 101 | class Meta: 102 | model = User 103 | fields = ("username", "password", "first_name", "last_name", "email") 104 | extra_kwargs = { 105 | 'password': {'write_only': True} 106 | } 107 | 108 | def create(self, validated_data): 109 | validated_data['password'] = make_password(validated_data['password']) 110 | user = super(RegistrationSerializer, self).create(validated_data) 111 | self.__notify_others(user) 112 | return validated_data 113 | 114 | def __notify_others(self, user): 115 | serializer = UserSerializer(user, many=False) 116 | channel_layer = get_channel_layer() 117 | async_to_sync(channel_layer.group_send)( 118 | 'notification', { 119 | 'type': 'new_user_notification', 120 | 'message': serializer.data 121 | } 122 | ) 123 | -------------------------------------------------------------------------------- /server/chat_app/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /server/chat_app/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from chat_app.views.auth_view import * 4 | from rest_framework.authtoken import views 5 | 6 | from chat_app.views.call_view import StartCall, EndCall 7 | from chat_app.views.message_view import MessageView 8 | from config import settings 9 | 10 | urlpatterns = [ 11 | path('api-token-auth/', views.obtain_auth_token), 12 | path('login/', Login.as_view()), 13 | path('registration/', RegisterView.as_view()), 14 | path('logout/', LogOutView.as_view()), 15 | path('users/', UsersView.as_view()), 16 | path('message/', MessageView.as_view()), 17 | path('start-call/', StartCall.as_view()), 18 | path('end-call/', EndCall.as_view()), 19 | path('test-socket/', test_socket) 20 | ] 21 | -------------------------------------------------------------------------------- /server/chat_app/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmrifat/django_chat/e50cd269c58150ca30d26740b715bbd76cebb856/server/chat_app/views/__init__.py -------------------------------------------------------------------------------- /server/chat_app/views/auth_view.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from asgiref.sync import async_to_sync 4 | from channels.layers import get_channel_layer 5 | from django.contrib.auth.models import User 6 | from django.core import serializers 7 | from django.http import HttpResponse 8 | 9 | # Create your views here. 10 | from django.shortcuts import render 11 | from rest_framework import generics 12 | from rest_framework.authentication import TokenAuthentication, BasicAuthentication, SessionAuthentication 13 | from rest_framework.authtoken.models import Token 14 | from rest_framework.authtoken.views import ObtainAuthToken 15 | from rest_framework.decorators import api_view 16 | from rest_framework.generics import CreateAPIView 17 | from rest_framework.permissions import IsAuthenticated 18 | from rest_framework.response import Response 19 | from rest_framework.views import APIView 20 | 21 | from chat_app.authentication import BearerAuthentication 22 | from chat_app.serializers import RegistrationSerializer, UsersWithMessageSerializer, UserSerializer 23 | 24 | 25 | class Login(ObtainAuthToken): 26 | 27 | def post(self, request, *args, **kwargs): 28 | """ 29 | 30 | @param request: 31 | @param args: 32 | @param kwargs: 33 | @return: 34 | """ 35 | serializer = self.serializer_class(data=request.data, context={'request': request}) 36 | serializer.is_valid(raise_exception=True) 37 | user = serializer.validated_data['user'] 38 | token, created = Token.objects.get_or_create(user=user) 39 | self.__change_status(user) 40 | serialize_user = UserSerializer(user, many=False) 41 | return Response({ 42 | 'token': token.key, 43 | 'user': serialize_user.data, 44 | }) 45 | 46 | def __change_status(self, user: User): 47 | """ 48 | @param user: 49 | """ 50 | profile = user.profile 51 | profile.online = True 52 | profile.save() 53 | notify_others(user) 54 | 55 | 56 | class RegisterView(CreateAPIView): 57 | serializer_class = RegistrationSerializer 58 | 59 | def post(self, request, *args, **kwargs): 60 | super(RegisterView, self).post(request, *args, **kwargs) 61 | return Response({'message': 'Registration success, now you can login'}) 62 | 63 | 64 | class LogOutView(APIView): 65 | authentication_classes = [SessionAuthentication, BasicAuthentication, BearerAuthentication] 66 | 67 | def post(self, request, format=None): 68 | profile = request.user.profile 69 | profile.online = False 70 | profile.save() 71 | notify_others(request.user) 72 | return Response({'message': 'logout'}) 73 | 74 | 75 | class UsersView(generics.ListAPIView): 76 | serializer_class = UsersWithMessageSerializer 77 | authentication_classes = [SessionAuthentication, BasicAuthentication, BearerAuthentication] 78 | permission_classes = [IsAuthenticated] 79 | 80 | def get_queryset(self): 81 | users = User.objects.exclude(pk=self.request.user.pk).order_by('-profile__online').all() 82 | return users 83 | 84 | 85 | def notify_others(user: User): 86 | """ 87 | 88 | @param user: 89 | @return: 90 | """ 91 | serializer = UserSerializer(user, many=False) 92 | channel_layer = get_channel_layer() 93 | async_to_sync(channel_layer.group_send)( 94 | 'notification', { 95 | 'type': 'user_online', 96 | 'message': serializer.data 97 | } 98 | ) 99 | 100 | 101 | def test_socket(request): 102 | # users = User.objects.all() 103 | # return render(request, template_name='test.html', context={'users': users}) 104 | # serializer = UserSerializer(user, many=False) 105 | channel_layer = get_channel_layer() 106 | async_to_sync(channel_layer.group_send)( 107 | 'chat_rifat', { 108 | 'type': 'new_call', 109 | 'message': { 110 | 'receiver': 'ritu', 111 | 'sender': 'rifat' 112 | } 113 | } 114 | ) 115 | return HttpResponse("hello world") 116 | -------------------------------------------------------------------------------- /server/chat_app/views/call_view.py: -------------------------------------------------------------------------------- 1 | from asgiref.sync import async_to_sync 2 | from channels.layers import get_channel_layer 3 | from django.contrib.auth.models import User 4 | from rest_framework import serializers, status 5 | from rest_framework.authentication import SessionAuthentication, BasicAuthentication 6 | from rest_framework.permissions import IsAuthenticated 7 | from rest_framework.response import Response 8 | from rest_framework.views import APIView 9 | 10 | from chat_app.authentication import BearerAuthentication 11 | from chat_app.serializers import UserSerializer 12 | 13 | 14 | class StartCallSerializer(serializers.Serializer): 15 | receiver = serializers.SlugField() 16 | sender = serializers.SlugField() 17 | peer_id = serializers.CharField() 18 | 19 | 20 | class StartCall(APIView): 21 | authentication_classes = [SessionAuthentication, BasicAuthentication, BearerAuthentication] 22 | permission_classes = [IsAuthenticated] 23 | 24 | def post(self, request, format=None): 25 | serializer = StartCallSerializer(data=request.data) 26 | if serializer.is_valid(): 27 | sender_user = User.objects.get(username=serializer.validated_data['sender']) 28 | channel_layer = get_channel_layer() 29 | async_to_sync(channel_layer.group_send)( 30 | 'chat_%s' % serializer.validated_data['receiver'], { 31 | 'type': 'new_call', 32 | 'message': { 33 | 'data': serializer.validated_data, 34 | 'display': UserSerializer(sender_user, context={'request': request}).data 35 | } 36 | } 37 | ) 38 | print('all good') 39 | return Response({'hello': 'world'}) 40 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 41 | 42 | 43 | class JoinCallSerializer(serializers.Serializer): 44 | peer_js = serializers.CharField() 45 | 46 | 47 | class EndCall(APIView): 48 | authentication_classes = [BearerAuthentication] 49 | permission_classes = [IsAuthenticated] 50 | 51 | def post(self, request, format=None): 52 | serializer = StartCallSerializer(data=request.data) 53 | if serializer.is_valid(): 54 | channel_layer = get_channel_layer() 55 | async_to_sync(channel_layer.group_send)( 56 | 'chat_%s' % serializer.validated_data['peer_id'], { 57 | 'type': 'end_call', 58 | 'message': { 59 | 'data': serializer.validated_data, 60 | } 61 | } 62 | ) 63 | return Response({'hello': 'world'}) 64 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 65 | -------------------------------------------------------------------------------- /server/chat_app/views/message_view.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from rest_framework.generics import CreateAPIView 3 | 4 | from chat_app.serializers import MessageModelSerializer, MessageSerializer 5 | 6 | 7 | class MessageView(CreateAPIView): 8 | serializer_class = MessageSerializer 9 | 10 | def post(self, request, *args, **kwargs): 11 | user = User.objects.get(pk=1) 12 | print(user.sender) 13 | return self.create(request, *args, **kwargs) 14 | -------------------------------------------------------------------------------- /server/chat_app/ws_urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from chat_app.consumers.message import MessageConsumer 4 | from chat_app.consumers.notification import NewUserConsumer 5 | 6 | websocket_urlpatterns = [ 7 | path('ws/notification/', NewUserConsumer.as_asgi()), 8 | path('ws/message//', MessageConsumer.as_asgi()) 9 | ] 10 | -------------------------------------------------------------------------------- /server/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmrifat/django_chat/e50cd269c58150ca30d26740b715bbd76cebb856/server/config/__init__.py -------------------------------------------------------------------------------- /server/config/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for config project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from channels.auth import AuthMiddlewareStack 13 | from channels.routing import ProtocolTypeRouter, URLRouter 14 | from django.core.asgi import get_asgi_application 15 | 16 | import chat_app.ws_urls 17 | 18 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 19 | 20 | application = ProtocolTypeRouter({ 21 | "http": get_asgi_application(), 22 | "websocket": AuthMiddlewareStack( 23 | URLRouter(chat_app.ws_urls.websocket_urlpatterns) 24 | ) 25 | }) 26 | -------------------------------------------------------------------------------- /server/config/routing.py: -------------------------------------------------------------------------------- 1 | from channels.auth import AuthMiddlewareStack 2 | from channels.routing import ProtocolTypeRouter, URLRouter 3 | 4 | import chat_app.ws_urls 5 | 6 | application = ProtocolTypeRouter({ 7 | 'websocket': AuthMiddlewareStack( 8 | URLRouter( 9 | chat_app.ws_urls.websocket_urlpatterns 10 | ) 11 | ) 12 | }) 13 | -------------------------------------------------------------------------------- /server/config/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for config project. 3 | 4 | Generated by 'django-admin startproject' using Django 4.0.3. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.0/ref/settings/ 11 | """ 12 | import os 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | # Quick-start development settings - unsuitable for production 19 | # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ 20 | 21 | # SECURITY WARNING: keep the secret key used in production secret! 22 | SECRET_KEY = 'django-insecure-_@69x&ao7t7w7-s(=n23st+!!c30c05n^a+%qtewjz-&0fqh7q' 23 | 24 | # SECURITY WARNING: don't run with debug turned on in production! 25 | DEBUG = True 26 | 27 | ALLOWED_HOSTS = [] 28 | 29 | # Application definition 30 | 31 | INSTALLED_APPS = [ 32 | 'django.contrib.admin', 33 | 'django.contrib.auth', 34 | 'django.contrib.contenttypes', 35 | 'django.contrib.sessions', 36 | 'django.contrib.messages', 37 | 'django.contrib.staticfiles', 38 | 'debug_toolbar', 39 | 'channels', 40 | 'rest_framework', 41 | 'rest_framework.authtoken', 42 | 'corsheaders', 43 | 'chat_app', 44 | ] 45 | 46 | MIDDLEWARE = [ 47 | 'django.middleware.security.SecurityMiddleware', 48 | 'django.contrib.sessions.middleware.SessionMiddleware', 49 | 'corsheaders.middleware.CorsMiddleware', 50 | 'django.middleware.common.CommonMiddleware', 51 | 'django.middleware.csrf.CsrfViewMiddleware', 52 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 53 | 'django.contrib.messages.middleware.MessageMiddleware', 54 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 55 | 'debug_toolbar.middleware.DebugToolbarMiddleware' 56 | ] 57 | 58 | REST_FRAMEWORK = { 59 | 'DEFAULT_AUTHENTICATION_CLASSES': [ 60 | 'rest_framework.authentication.BasicAuthentication', 61 | 'rest_framework.authentication.SessionAuthentication', 62 | 'rest_framework.authentication.TokenAuthentication', 63 | 'chat_app.authentication.BearerAuthentication' 64 | ] 65 | } 66 | 67 | ROOT_URLCONF = 'config.urls' 68 | 69 | TEMPLATES = [ 70 | { 71 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 72 | 'DIRS': [BASE_DIR / 'templates'], 73 | 'APP_DIRS': True, 74 | 'OPTIONS': { 75 | 'context_processors': [ 76 | 'django.template.context_processors.debug', 77 | 'django.template.context_processors.request', 78 | 'django.contrib.auth.context_processors.auth', 79 | 'django.contrib.messages.context_processors.messages', 80 | ], 81 | }, 82 | }, 83 | ] 84 | 85 | WSGI_APPLICATION = 'config.wsgi.application' 86 | 87 | # Database 88 | # https://docs.djangoproject.com/en/4.0/ref/settings/#databases 89 | 90 | DATABASES = { 91 | 'default': { 92 | 'ENGINE': 'django.db.backends.sqlite3', 93 | 'NAME': BASE_DIR / 'db.sqlite3', 94 | } 95 | } 96 | 97 | # Password validation 98 | # https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators 99 | 100 | AUTH_PASSWORD_VALIDATORS = [ 101 | # { 102 | # 'NAME': 'django.contrib.chat_app.password_validation.UserAttributeSimilarityValidator', 103 | # }, 104 | # { 105 | # 'NAME': 'django.contrib.chat_app.password_validation.MinimumLengthValidator', 106 | # }, 107 | # { 108 | # 'NAME': 'django.contrib.chat_app.password_validation.CommonPasswordValidator', 109 | # }, 110 | # { 111 | # 'NAME': 'django.contrib.chat_app.password_validation.NumericPasswordValidator', 112 | # }, 113 | ] 114 | 115 | # Internationalization 116 | # https://docs.djangoproject.com/en/4.0/topics/i18n/ 117 | 118 | LANGUAGE_CODE = 'en-us' 119 | 120 | TIME_ZONE = 'UTC' 121 | 122 | USE_I18N = True 123 | 124 | USE_TZ = True 125 | 126 | # Static files (CSS, JavaScript, Images) 127 | # https://docs.djangoproject.com/en/4.0/howto/static-files/ 128 | 129 | STATIC_URL = 'static/' 130 | 131 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 132 | 133 | MEDIA_URL = '/media/' 134 | 135 | # Default primary key field type 136 | # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field 137 | 138 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 139 | 140 | ASGI_APPLICATION = 'config.asgi.application' 141 | 142 | CHANNEL_LAYERS = { 143 | 'default': { 144 | 'BACKEND': 'channels_redis.core.RedisChannelLayer', 145 | 'CONFIG': { 146 | "hosts": [('127.0.0.1', 6379)], 147 | }, 148 | }, 149 | } 150 | 151 | CORS_ALLOWED_ORIGINS = [ 152 | 'https://localhost:3000', 153 | 'https://127.0.0.1:3000', 154 | 'https://192.168.31.39:3000' 155 | ] 156 | 157 | INTERNAL_IPS = [ 158 | "127.0.0.1", 159 | ] 160 | -------------------------------------------------------------------------------- /server/config/urls.py: -------------------------------------------------------------------------------- 1 | """config URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/4.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls.static import static 17 | from django.contrib import admin 18 | from django.urls import path, include 19 | 20 | from config import settings 21 | 22 | urlpatterns = [ 23 | path('__debug__/', include('debug_toolbar.urls')), 24 | path('admin/', admin.site.urls), 25 | path('chat-app/', include('chat_app.urls')) 26 | 27 | ] 28 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 29 | -------------------------------------------------------------------------------- /server/config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for config project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /server/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /server/media/dr_assistant_pro_customization.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmrifat/django_chat/e50cd269c58150ca30d26740b715bbd76cebb856/server/media/dr_assistant_pro_customization.drawio.png -------------------------------------------------------------------------------- /server/media/girl.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /server/templates/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | {% for user in users %} 10 | {{ user.sender.all }} 11 | {{ user.reciever.all }} 12 | {% endfor %} 13 | 14 | 15 | --------------------------------------------------------------------------------