diff options
191 files changed, 7746 insertions, 823 deletions
@@ -22,9 +22,13 @@ /config/application.yml # The rest is from Luke. +/config/database.yml /vendor/bundle -nohup.out +/public/assets +/*.war + # As noted above, you probably want to add the following to your global git config. +nohup.out .~lock.* *~ *# diff --git a/COPYING.txt b/COPYING.txt new file mode 100644 index 0000000..dba13ed --- /dev/null +++ b/COPYING.txt @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<http://www.gnu.org/licenses/>. @@ -13,8 +13,14 @@ gem 'httparty' gem 'simple_captcha2', require: 'simple_captcha' group :development, :test do - # Use sqlite3 as the database for Active Record - gem 'sqlite3' + # Use sqlite3 as the database + gem 'sqlite3', platforms: [ :mri ] + gem 'activerecord-jdbcsqlite3-adapter', platforms: [ :jruby ] +end +group :production do + # USe PostgresQL as the database + gem 'pg', platforms: [ :mri ] + gem 'activerecord-jdbcpostgresql-adapter', platforms: [ :jruby ] end # group :test do @@ -34,7 +40,8 @@ gem 'uglifier', '>= 1.3.0' gem 'coffee-rails', '~> 4.0.0' # See https://github.com/sstephenson/execjs#readme for more supported runtimes -gem 'therubyracer', platforms: :ruby +gem 'therubyracer', platforms: [ :mri ] +gem 'therubyrhino', platforms: [ :jruby ] # Use jquery as the JavaScript library gem 'jquery-rails' @@ -59,6 +66,7 @@ group :doc do gem 'sdoc', require: false end +gem "warbler", require: false, platforms: [ :jruby ], groups: [ :development ] # Use ActiveModel has_secure_password # gem 'bcrypt-ruby', '~> 3.1.2' diff --git a/app/assets/images/Plus.png b/app/assets/images/Plus.png Binary files differnew file mode 100644 index 0000000..fa6a7a5 --- /dev/null +++ b/app/assets/images/Plus.png diff --git a/app/assets/images/bg.png b/app/assets/images/bg.png Binary files differnew file mode 100644 index 0000000..91c77c8 --- /dev/null +++ b/app/assets/images/bg.png diff --git a/app/assets/images/chess.png b/app/assets/images/chess.png Binary files differnew file mode 100644 index 0000000..6bcffe6 --- /dev/null +++ b/app/assets/images/chess.png diff --git a/app/assets/images/hearthstone.png b/app/assets/images/hearthstone.png Binary files differnew file mode 100644 index 0000000..15d20b4 --- /dev/null +++ b/app/assets/images/hearthstone.png diff --git a/app/assets/images/league_of_legends.png b/app/assets/images/league_of_legends.png Binary files differnew file mode 100644 index 0000000..9a78047 --- /dev/null +++ b/app/assets/images/league_of_legends.png diff --git a/app/assets/images/rock_paper_scissors.png b/app/assets/images/rock_paper_scissors.png Binary files differnew file mode 100644 index 0000000..294916c --- /dev/null +++ b/app/assets/images/rock_paper_scissors.png diff --git a/app/assets/images/rock_paper_scissors.svg b/app/assets/images/rock_paper_scissors.svg new file mode 100644 index 0000000..67bb6bd --- /dev/null +++ b/app/assets/images/rock_paper_scissors.svg @@ -0,0 +1,108 @@ +<?xml version="1.0"?> +<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:svg="http://www.w3.org/2000/svg" id="svg2" viewBox="0 0 691.81 691.81" version="1.1"> + <title id="title4919">Rock Scissors Paper</title> + <defs id="defs4"> + <marker id="Arrow2Mend" refY="0" refX="0" overflow="visible" orient="auto"> + <path id="path4038" stroke-linejoin="round" d="m8.7186 4.0337-10.926-4.0177 10.926-4.0177c-1.7455 2.3721-1.7354 5.6175-6e-7 8.0354z" fill-rule="evenodd" transform="scale(-.6)" stroke-width=".625"/> + </marker> + </defs> + <rect x="0" y="0" width="691.81" height="691.81" style="fill: #AAAAAA" /> + <g id="layer1" transform="translate(-42.612 -137.16)"> + <g id="g4875" transform="matrix(.77244 0 0 .77244 192.28 -14.824)"> + <g id="g2906" transform="matrix(-.84212 .17678 .17678 .84212 677.84 268.6)"> + <path id="path3" d="m259.08 17.719c-4.01-0.256-2.042 5.467-6.258 5.006-2.182-2.162-1.385-7.737 1.251-8.761 0.281 0.971 0.035 2.469 1.252 2.503 0.28-0.97 0.034-2.468 1.251-2.503 0.299 1.786 2.062 2.111 2.504 3.755z"/> + <path id="path5" d="m173.97 17.719c-1.584 1.436-9.868 2.515-11.265 0 4.306 0.479 7.803-6.286 11.265-2.503-1.511 0.264-0.771 1.389 0 2.503z"/> + <path id="path7" d="m240.3 20.221c-1.901 1.276-8.113-1.155-10.014-3.754 5.03-0.44 9.158 0.02 10.014 3.754z"/> + <path id="path9" d="m290.36 57.769c1.164-2.922 4.529-10.066 0-5.006-1.437-2.351-0.207-10.399 5.006-10.013 0.505 7.179 0.247 13.597-5.006 15.019z"/> + <path id="path11" d="m112.88 15.406h-18.773c-0.743-0.873 29.706-4.351 18.773 0z"/> + <path id="path13" d="m369.21 86.556c-0.326-1.948-2.745-0.868-3.754 0 3.313-2.11 1.396-9.449 3.754-12.516 1.525 1.894 2.458 10.78 0 12.516z"/> + <path id="path15" d="m295.37 89.058c1.109-4.731 0.937-10.745 3.756-13.767-0.142 5.699-0.024 11.657-3.756 13.767z"/> + <path id="path17" d="m191.49 85.303c-7.078-0.848-17.994 2.142-21.276-2.503 9.66 0.83 16.427-3.137 21.276 2.503z"/> + <path id="path19" d="m334.17 125.35c-1.795-1.543-2.371-4.304-2.503-7.51 2.142-1.195 2.521-4.152 5.006-5.005-1.2 3.807 2.68 12.692-2.503 12.515z"/> + <path id="path21" d="m289.11 124.1c0.39-3.784-1.39-9.734 2.503-10.013-1.03 3.141 2.434 10.775-2.503 10.013z"/> + <path id="path23" d="m188.99 126.61c-4.574 0.851-9.1 1.746-15.019 1.251 1.243-3.813 9.789-6.836 13.769-3.754-7.763 0.486 0.084 0.641 1.25 2.503z"/> + <path id="path25" d="m200.25 169.16c-0.073-5.496 0.504-10.345 2.503-13.768-1.261 4.163 2.766 13.615-2.503 13.768z"/> + <path id="path27" d="m158.95 46.505c5.797 0.809 10.295-4.935 15.019 0-5.767-1.534-11.103 3.833-15.019 0z"/> + <path id="path29" d="m46.229 20.57c8.204 0.277 14.635-1.219 21.277-2.503-2.471 4.077-20.887 4.58-21.277 2.503z"/> + <path id="path31" d="m281.6 126.61c1.312-2.515 0.599-13.491 2.503-11.265 0.411 4.236-0.976 13.112-2.503 11.265z"/> + <path id="path33" d="m285.36 129.11c0.745-6.283 0.367-10.093 1.252-13.768 2.044 2.957 2.173 12.365-1.252 13.768z"/> + <path id="path35" d="m367.96 126.6c-5.002 1.868-15.024 1.868-20.025 0-1.197-7.076 1.841-16.464 0-20.024-46.83 1.298-86.581 1.938-135.17 1.251 20.958-7.99 48.129-3.82 75.094-5.007 43.017-1.89 87.378 1.49 122.65-7.509 4.33-5.413 3.127-19.587-2.503-22.527 2.882 6.014 0.101 13.255-2.503 17.521-8.105 0.18-16.143 0.289-21.276-2.502 0.216-7.711-1.787-17.64 5.006-18.773-31.898 1.353-56.328-3.795-86.357-1.252 0.331 5.869 3.998 16.742-2.503 18.773 1.253-5.3 2.223-13.686 0-18.773-29.323-3.635-65.038-0.877-91.364-7.51 62.248-1.654 128.88 7.685 183.98 1.252-0.668-4.338 1.859-11.87-2.503-12.516 0.728 4.898-1.077 7.267-2.503 10.013-8.161-0.962-16.246 1.611-21.275 0 0.783-3.804-1.603-10.78 1.252-12.516-5.466 15.413 17.483 12.443 20.023 5.006-6.653-8.223-20.667-9.609-32.541-11.264-47.229-6.583-111.25 0.445-155.19-10.013 36.4 2.273 86.049 1.268 121.4 2.503 7.167 0.251 22.518 6.253 21.276-7.509-3.388 4.539-11.951 3.901-20.025 3.754-2.494-1.276 2.634-8.914-1.251-11.264-51.09-5.314-113.91-14.316-167.7-11.256-41.5 2.359-82.498-0.122-117.64 5.006-12.469 1.817-24.626 7.461-36.298 5.006 22.245-7.795 47.513-9.995 75.093-11.263 32.795-1.507 69.275-6.218 101.38-5.007 6.743 0.256 13.555 3.074 20.025 3.756 52.454 5.521 106.54 1.751 148.94 17.521 3.519 3.539 1.673 11.946 2.503 15.02 22.246 3.101 56.888-0.582 48.811 28.785 5.949-0.524 9.027 1.819 13.769 2.503 4.653 6.229 8.617 18.292 5.005 28.787-7.016 9.671-28.406 4.97-41.302 8.762 7.52 10.533 1.729 26.649-6.257 32.54-31.736 6.23-69.292 6.638-106.38 7.51 5.988 5.631 10.071 17.214 6.259 28.786-27.447 16.401-68.675 7.55-100.12 12.516-15.694 2.479-21.298 6.535-33.792 7.51-54.56 4.252-64.943-32.562-103.88-47.561-5.108-1.966-11.236-2.437-16.271-3.754-3.554-0.933-13.292 0.196-13.767-7.51 0.09-5.264 13.964 2.216 15.02 0-1.541-2.722-13.286-6.875-7.511-8.762 35.521 12.869 61.882 56.016 106.38 61.327 22.711 2.71 44.086-4.475 67.584-6.259 25.915-1.966 51.778 2.017 75.094-6.258-5.259-2.249-14.193-0.824-20.024-2.503-0.108-7.2-0.271-14.455 1.252-20.025 6.923-2.259 13.888 0.48 21.276 1.252 3.234 5.568 2.444 14.378 3.754 18.773 17.154-49.229-104.77-10.208-123.9-36.295 32.543 9.766 100.99 5.861 148.94 3.754 34.288-1.507 102.81 3.411 81.353-31.289 2.749 4.647 1.047 13.937-1.256 17.522zm-41.3-108.88c-1.144 2.194-0.43 6.245-2.503 7.51 4.209 1.758 10.931 2.934 15.021 1.251 1.364-8.457-6.901-7.285-12.518-8.761zm60.076 55.068v13.768c4.332 1.925 11.41 1.106 17.522 1.252 8.308-11.294-5.278-20.534-17.522-15.02zm-35.044 37.548c-2.397 3.025-0.789 10.057-1.251 15.019h13.768c9.719-7.536-0.683-22.006-12.517-15.019zm-111.39 45.057c-1.666 3.758-1.273 9.576-1.252 15.021 7.063-0.39 11.13 2.219 18.773 1.251 2.625-3.851 1.255-12.357-1.252-15.021-6.133 0.292-9.475-2.205-16.269-1.251z"/> + </g> + <g id="g2982" transform="matrix(-.33587 -.48446 .48446 -.33587 255.64 813.8)" fill-rule="evenodd"> + <path id="path3-4" d="m0 129.73c5.056 1.287 3.347 9.338 10.379 8.649 2.95-0.709-0.364-2.23 0-5.189 4.51 2.985 8.768 6.223 8.649 13.838-6.934-2.29-11.824-6.629-19.028-8.649v-8.649z"/> + <path id="path5-0" d="m19.028 131.46c3.663 16.518 24.55 15.811 34.596 25.947-11.769-1.308-20.868-9.217-29.407-8.649 0.714-8.21-5.903-9.088-5.189-17.298z"/> + <path id="path7-9" d="m247.36 153.95c9.951-0.126 16.131 18.447 20.758 29.405-8.991-7.729-11.192-22.252-20.758-29.405z"/> + <path id="path9-4" d="m385.74 205.84c4.146 3.703-5.014 8.726-10.379 8.649 1.757-4.586 7.999-4.686 10.379-8.649z"/> + <path id="path11-8" d="m321.74 243.9c4.889 3.89-2.845 9.726-3.46 13.838-4.889-3.89 2.842-9.726 3.46-13.838z"/> + <path id="path13-8" d="m0 153.95c10.402 3.682 40.146 8.392 53.624 13.838 40.282 16.275 58.092 66.777 124.55 51.894-6.811-5.875-15.101-10.27-22.488-15.567 13.222 1.081 20.92 12.012 34.596 17.297 7.584 2.931 18.474 2.089 27.677 6.92 11.564 6.071 21.198 19.362 32.866 27.677 24.197 17.243 40.023 31.271 64.002 6.919-3.854 7.102-9.293 12.617-19.027 13.838 18.444 3.007 35.974-11.953 36.325-25.946 0.427-16.97-25.394-39.572-36.325-57.084-19.65-31.474-23.18-54.689-36.326-79.57 5.988-3.042 6.72 13.99 13.838 15.568 7.606-5.078 5.466-19.906 15.568-22.487-1.614 8.764-4.848 15.91-6.918 24.217 5.624 3.309 9.881 3.427 13.838 1.73 6.081-24.971 9.578-51.964 32.865-55.354-13.657 11.844-36.839 46.024-15.568 65.732 14.236-11.135 18.784-31.958 34.597-41.515 3.048 6.002-1.768 7.836 8.648 5.189-5.946 5.585-16.236 6.826-20.757 13.838 2.43 2.873 9.672-4.067 17.297-1.73-15.773 4.983-27.891 13.624-31.135 31.137 1.87 12.262 21.477 13.787 25.946 32.865 1.311 5.597-2.26 12.064-1.729 17.299 2.02 19.947 29.403 38.392 39.785 15.568 2.611 5.559-0.23 6.872 5.189 6.919 8.24-0.327 15.966-9.178 17.298-20.758 5.03-43.67-5.31-129.17-20.76-152.23-5.61-8.383-16.99-15.295-29.4-17.293-58.25-9.38-117.67 20.656-172.98 20.757-68.45 0.125-126.98-38.346-185.09-48.435v-5.189c28.95 8.619 55.573 15.987 83.03 24.217 25.389 7.61 52.703 19.662 83.03 22.488 34.634 3.226 63.573-4.378 93.409-10.379 44.027-8.854 116.44-25.423 136.65 13.838 10.15 19.708 10.938 52.111 13.839 74.381 3.635 27.895 10.597 61.064 5.189 83.03-1.816 7.374-10.973 14.042-17.298 25.946-5.971 11.24-8.695 21.203-15.568 25.947-4.525 3.125-12.553 0.71-20.757 5.189-8.051 4.396-10.497 15.109-22.488 19.028-8.403 2.746-20.753 1.121-31.136 3.459-12.255 2.761-19.113 9.001-27.677 8.649-13.187-0.541-39.07-11.738-53.624-22.488-7.122-5.26-9.862-15.02-17.298-20.757-27.34-21.11-82.78-17.64-112.42-31.14-22.143-10.08-32.794-35.57-50.167-43.24-13.597-6.01-35.367-5.07-46.703-8.65v-15.57zm364.99 102.06c3.907-6.804 8.095-2.571 12.109-8.649 2.249 4.296-5.075 7.179 0 8.649 4.095-6.283 10.517-10.24 12.108-19.028-11.298-0.811-22.458-1.761-32.867-3.46-2.915 8.062-3.552 24.269 8.65 22.488z"/> + <path id="path15-2" d="m219.68 140.11c1.396-5.323 10.558 10.831 10.379 1.729 6.086 5.444 12.56 10.504 13.838 20.758-9.095-6.473-15.122-16.014-24.217-22.487z"/> + <path id="path17-4" d="m164.33 162.6c1.46-2.458 8.919 3.65 10.379 6.919 2.568-0.755-0.683-2.213 0-5.189 7.596 6.242 14.36 13.316 22.487 19.027-10.021-0.957-25.064-11.824-32.866-20.757z"/> + <path id="path19-5" d="m207.58 138.38c-6.535-3.267-16.759-2.845-19.027-10.379 7.358-3.752 17.359 2.874 19.027 10.379z"/> + </g> + <g id="g3028" transform="matrix(.94236 .33461 -.33461 .94236 -172.81 169.56)" fill-rule="evenodd"> + <path id="path3-1" d="m361.4 18.798c0.183 3.935-2.72 8.736 0.351 11.859 0.581-1.918 2.404-2.862 1.882-1.278 3.681-0.369-0.205-4.866 1.708-7.206-2.92-0.673-1.599-2.539-3.941-3.375z"/> + <path id="path5-7" d="m323.56 19.741c-1.958 1.333-1.575 8.389 0.848 10.972 2.115-4.334-1.078-7.171-0.848-10.972z"/> + <path id="path7-1" d="m373.98 89.99c-3.714 1.095-4.415 7.683-4.585 10.26 3.952-1.163 2.957-7.273 4.585-10.26z"/> + <path id="path9-1" d="m321.97 72.5c-2.411 1.459-2.76 8.722-4.691 12.531 4.131-1.946 5.219-9.416 4.691-12.531z"/> + <path id="path11-5" d="m270.18 127.44c-4.752 9.811-19.64 1.719-24.672-8 5.153-4.7 10.842-7.498 16.442-10.608 2.885 6.549 11.457 11.943 8.23 18.608zm-21.122-6.01c4.796 2.948 9.505 7.987 15.086 8.457 11.746-11.772-8.793-23.322-15.086-8.457z"/> + <path id="path13-2" d="m253.65 111.17c-2.67-1.188-6.125 1.818-5.647 3.832 2.115-0.45 3.98-1.786 5.647-3.832z"/> + <path id="path15-7" d="m2.984 27.804c19.307-0.023 39.398-2.352 60.289-3.524 19.921-1.12 40.249-0.095 60.179-1.253 17.961-1.042 38.129-5.02 57.842-9.561 20.324-4.681 39.488-12.878 57.559-13.218 13.051-0.246 29.157 4.603 43.064 4.315 9.181-0.19 19.653-2.939 30.252-4.035 21.304-2.203 44.943 3.055 65.787 5.389 12.365 1.385 29.646 4.323 29.889 13.256 0.524 19.289-27.008 14.056-36.328 16.955-13.509 1.351-27.446-2.572-41.397-1.047-7.37 0.806-14.535 4.626-21.66 5.351-9.644 0.983-20.19-3.831-27.589 5.527 42.685 20.521 87.223 26.455 127.48 49.3 5.767 34.513-41.071 12.854-62.277 7.074-26.268-7.161-48.3-16.484-64.968-13.095-0.35 10.063 16.708 15.225 17.493 24.967-10.56 23.538-31.963 8.575-42.513 32.15-10.699-0.138-23.185 6.185-32.632 6.199-7.419 0.01-15.125-4.914-25.168-7.113-23.599-5.165-59.929-1.638-81.196-7.028-22.981-5.826-35.052-17.105-52.047-7.019-1.775-0.994-3.549-1.988-5.325-2.983 8.888-5.595 19.42-5.348 29.083-8.189 0.795-1.107-2.106-4.701 1.102-4.046-0.22 1.557 0.171 2.94 1.171 4.154 0.967-2.802-1.314-8.549 1.813-9.479 0.1 3.21-0.55 6.631 1.843 9.196 21.318-4.805 47.895-5.07 65.761-13.287-18.073 0.189-38.065 2.406-58.541-14.155 28.437 15.536 61.903 14.136 83.4-1.064-5.337-3.071-11.74-3.682-13.7-8.847 1.478 0.828 2.957 1.657 4.436 2.487-1.045-6.191-9.188-14.342-6.843-18.997-0.98 21.186 32.324 21.716 50.648 36.556 3.521 2.852 4.986 8.622 8.552 11.79 12.459 11.079 38.325 27.914 43.629 11.63 1.475-4.527-5.874-17.871-9.398-22.763-6.85-9.518-17.31-22.53-25.97-28.56-15.51-10.8-35.67-11.84-39.22-27.814 5.059 3.557 6.6 8.104 13.593 11.116 3.937-3.393-0.896-8.159 2.702-8.98 0.786 2.865 0.175 8.934 4.612 8.417 3.356-4.005 6.206-9.805 10.124-11.819-2.581 5.461-8.058 11.736-6.682 16.081 14.856 5.141 28.032 15.589 34.783 25.328 31.917-10.33 73.244 4.919 112.58 17.624 10.837 3.501 33.131 11.804 36.853 0.833 2.115-6.233-8.018-11.988-14.763-15.274-34.38-16.744-79.11-26.327-115.55-41.446-1.908-0.621-8.7 3.843-6.816-0.324 10.694-11.2 25.805-10.954 39.771-12.692 5.622-0.7 11.034-2.603 17.008-3.294 26.229-3.041 50.338 4.846 68.879-2.207 6.501-2.474 12.802-8.383 8.456-15.085-33.85-7.884-68.86-11.57-103.92-8.105-18.22 1.801-33.92 0.942-50.98-0.589-32.88-2.953-66.64 12.387-96.51 18.207-16.4 3.195-32.97 1.52-50.21 2.181-33.113 1.266-69.593 3.773-101.4 6.13 0.995-1.775 1.99-3.55 2.984-5.324zm261.6 63.842c0.395 11.847 9.998 21.099 12.129 32.457 6.438 0.748 12.617 0.576 16.336-8.335-5.655-8.494-16.405-22.274-28.465-24.122zm-43.467 23.451c-2.333-0.235 0.908 10.869 4.329 4.76-2.22-1.369-3.948-2.875-4.329-4.76zm34.5 21.673c-11.813-7.808-30.909-17.961-31.703-3.777-8.923-9.287-27.699-17.935-34.863-4.381 7.407-3.651 15.946-0.771 21.513 7.394-0.996 1.774-1.99 3.55-2.985 5.324 4.788 2.848 9.016 3.707 13.418 5.189 8.938-5.838 28.133 3.156 34.62-9.749zm-64.013-4.395c4.305 3.021 6.208 6.721 12.316 9.237 0.995-1.775 1.99-3.551 2.984-5.325-2.4-4.755-11.471-9.038-15.3-3.912z"/> + <path id="path17-6" d="m320.29 21.408c-1.362-2.508-1.234 5.898-0.82 7.705 3.498-0.554 0.371-5.536 0.82-7.705z"/> + <path id="path19-1" d="m239.77 96.394c-2.115 0.449-3.981 1.787-5.646 3.832-0.551-3.857 6.066-4.896 7.354-11.038-5.153 2.885-9.686 10.019-12.503 13.983 3.877-1.264 7.735-2.605 10.795-6.777z"/> + <path id="path21-4" d="m61.931 91.17c1.902 1.954 2.553 4.263 1.951 6.924-16.353-0.565-31.533 3.027-47.544 3.672 15.331 5.78 32.779-3.897 50.486 1.477-0.191-3.683-1.925-6.93-2.231-10.58 1.717-3.636 5.314-0.588 7.032-4.223-20.932-0.899-40.87 1.712-60.396 5.797 14.697 1.758 33.589-4.559 50.702-3.067z"/> + </g> + <path id="path3234" marker-end="url(#Arrow2Mend)" d="m192.86 538.08-65.72-114.29" stroke="#000" stroke-width="4" fill="none"/> + <path id="path3234-2" marker-end="url(#Arrow2Mend)" d="m193.47 352.93 130.62 17.825" stroke="#000" stroke-width="4" fill="none"/> + <path id="path3234-8" marker-end="url(#Arrow2Mend)" d="m416.72 467.9-105.26 79.37" stroke="#000" stroke-width="4" fill="none"/> + </g> + </g> + <metadata> + <rdf:RDF> + <cc:Work> + <dc:format>image/svg+xml</dc:format> + <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> + <cc:license rdf:resource="http://creativecommons.org/licenses/publicdomain/"/> + <dc:publisher> + <cc:Agent rdf:about="http://openclipart.org/"> + <dc:title>Openclipart</dc:title> + </cc:Agent> + </dc:publisher> + <dc:title>Rock Scissors Paper</dc:title> + <dc:date>2010-05-31T17:54:35</dc:date> + <dc:description>Rock Scissors Paper hand game depiction. Rock, scissors, and paper by Francesco 'Architetto' Rollandin from openclipart.org</dc:description> + <dc:source>http://openclipart.org/detail/63805/rock-scissors-paper-by-mazeo</dc:source> + <dc:creator> + <cc:Agent> + <dc:title>mazeo</dc:title> + </cc:Agent> + </dc:creator> + <dc:subject> + <rdf:Bag> + <rdf:li>clip art</rdf:li> + <rdf:li>clipart</rdf:li> + <rdf:li>fingers</rdf:li> + <rdf:li>fist</rdf:li> + <rdf:li>hand</rdf:li> + <rdf:li>hands</rdf:li> + <rdf:li>paper</rdf:li> + <rdf:li>paper rock scissors</rdf:li> + <rdf:li>paper scissors rock</rdf:li> + <rdf:li>remix</rdf:li> + <rdf:li>rock</rdf:li> + <rdf:li>rock paper scissors</rdf:li> + <rdf:li>rock scissors paper</rdf:li> + <rdf:li>scissor</rdf:li> + <rdf:li>scissors</rdf:li> + <rdf:li>scissors paper rock</rdf:li> + <rdf:li>scissors rock paper</rdf:li> + </rdf:Bag> + </dc:subject> + </cc:Work> + <cc:License rdf:about="http://creativecommons.org/licenses/publicdomain/"> + <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/> + <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/> + </cc:License> + </rdf:RDF> + </metadata> +</svg> diff --git a/app/assets/javascripts/alerts/edit.js.coffee b/app/assets/javascripts/alerts/edit.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/alerts/edit.js.coffee diff --git a/app/assets/javascripts/alerts/index.js.coffee b/app/assets/javascripts/alerts/index.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/alerts/index.js.coffee diff --git a/app/assets/javascripts/alerts/new.js.coffee b/app/assets/javascripts/alerts/new.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/alerts/new.js.coffee diff --git a/app/assets/javascripts/alerts/show.js.coffee b/app/assets/javascripts/alerts/show.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/alerts/show.js.coffee diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index d6925fa..1130838 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -13,4 +13,8 @@ //= require jquery //= require jquery_ujs //= require turbolinks -//= require_tree . +//= require 'drag.js' +//= require 'dragsort.js' +//= require 'coordinates.js' +// +//= require_tree ./application diff --git a/app/assets/javascripts/application/.keep b/app/assets/javascripts/application/.keep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/application/.keep diff --git a/app/assets/javascripts/application/layout.js.coffee b/app/assets/javascripts/application/layout.js.coffee new file mode 100644 index 0000000..19b0b8a --- /dev/null +++ b/app/assets/javascripts/application/layout.js.coffee @@ -0,0 +1,43 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + +json_url = "/alerts.json" + +page_visited = false +starting_size = 0 +update = (alerts) -> + if !page_visited + starting_size = alerts.length + page_visited = true + + if alerts.length > starting_size + $("#alerts-ajax").css("display", "inline"); + return + + setTimeout (-> + $.ajax(url: json_url).done update + return + ), 2000 + +# Now kick off the whole process +window.onload = -> + $.ajax(url: json_url).done update
\ No newline at end of file diff --git a/app/assets/javascripts/brackets/edit.js.coffee b/app/assets/javascripts/brackets/edit.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/brackets/edit.js.coffee diff --git a/app/assets/javascripts/brackets/index.js.coffee b/app/assets/javascripts/brackets/index.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/brackets/index.js.coffee diff --git a/app/assets/javascripts/brackets/new.js.coffee b/app/assets/javascripts/brackets/new.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/brackets/new.js.coffee diff --git a/app/assets/javascripts/brackets/show.js.coffee b/app/assets/javascripts/brackets/show.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/brackets/show.js.coffee diff --git a/app/assets/javascripts/games/edit.js.coffee b/app/assets/javascripts/games/edit.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/games/edit.js.coffee diff --git a/app/assets/javascripts/games/index.js.coffee b/app/assets/javascripts/games/index.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/games/index.js.coffee diff --git a/app/assets/javascripts/games/new.js.coffee b/app/assets/javascripts/games/new.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/games/new.js.coffee diff --git a/app/assets/javascripts/games/show.js.coffee b/app/assets/javascripts/games/show.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/games/show.js.coffee diff --git a/app/assets/javascripts/main/homepage.js.coffee b/app/assets/javascripts/main/homepage.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/main/homepage.js.coffee diff --git a/app/assets/javascripts/matches/show.js.coffee b/app/assets/javascripts/matches/show.js.coffee new file mode 100644 index 0000000..2609a69 --- /dev/null +++ b/app/assets/javascripts/matches/show.js.coffee @@ -0,0 +1,2 @@ +window.onload = -> + BetterDragSort.makeListSortable(document.getElementById("boxes")); diff --git a/app/assets/javascripts/pms/edit.js.coffee b/app/assets/javascripts/pms/edit.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/pms/edit.js.coffee diff --git a/app/assets/javascripts/pms/index.js.coffee b/app/assets/javascripts/pms/index.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/pms/index.js.coffee diff --git a/app/assets/javascripts/pms/new.js.coffee b/app/assets/javascripts/pms/new.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/pms/new.js.coffee diff --git a/app/assets/javascripts/pms/show.js.coffee b/app/assets/javascripts/pms/show.js.coffee new file mode 100644 index 0000000..8397fbe --- /dev/null +++ b/app/assets/javascripts/pms/show.js.coffee @@ -0,0 +1,47 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + +json_url = window.location.href.replace(/\.[^/]*$/,'')+".json" + +page_visited_pms = false +starting_size_pms = 0 +update = (pms) -> + if !page_visited_pms + starting_size_pms = pms.conversation.count_messages + page_visited_pms = true + + if pms.convesation.count_messages > starting_size_pms + window.location.reload true + return + + console.log("hey we got here!") + console.log(starting_size_pms) + console.log(pms.convesation.count_messages) + + setTimeout (-> + $.ajax(url: json_url).done update + return + ), 2000 + +# Now kick off the whole process +window.onload = -> + $.ajax(url: json_url).done update
\ No newline at end of file diff --git a/app/assets/javascripts/search/go.js.coffee b/app/assets/javascripts/search/go.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/search/go.js.coffee diff --git a/app/assets/javascripts/server/edit.js.coffee b/app/assets/javascripts/server/edit.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/server/edit.js.coffee diff --git a/app/assets/javascripts/server/show.js.coffee b/app/assets/javascripts/server/show.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/server/show.js.coffee diff --git a/app/assets/javascripts/sessions/new.js.coffee b/app/assets/javascripts/sessions/new.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/sessions/new.js.coffee diff --git a/app/assets/javascripts/teams/edit.js.coffee b/app/assets/javascripts/teams/edit.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/teams/edit.js.coffee diff --git a/app/assets/javascripts/teams/index.js.coffee b/app/assets/javascripts/teams/index.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/teams/index.js.coffee diff --git a/app/assets/javascripts/teams/new.js.coffee b/app/assets/javascripts/teams/new.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/teams/new.js.coffee diff --git a/app/assets/javascripts/teams/show.js.coffee b/app/assets/javascripts/teams/show.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/teams/show.js.coffee diff --git a/app/assets/javascripts/tournaments/edit.js.coffee b/app/assets/javascripts/tournaments/edit.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/tournaments/edit.js.coffee diff --git a/app/assets/javascripts/tournaments/index.js.coffee b/app/assets/javascripts/tournaments/index.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/tournaments/index.js.coffee diff --git a/app/assets/javascripts/tournaments/new.js.coffee b/app/assets/javascripts/tournaments/new.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/tournaments/new.js.coffee diff --git a/app/assets/javascripts/tournaments/show.js.coffee b/app/assets/javascripts/tournaments/show.js.coffee new file mode 100644 index 0000000..1fa9916 --- /dev/null +++ b/app/assets/javascripts/tournaments/show.js.coffee @@ -0,0 +1,54 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + +json_url = window.location.href.replace(/\.[^/]*$/,'')+".json" + +update = (tournament) -> + here = tournament["players"].length + needed = (tournament["min_teams_per_match"] * tournament["min_players_per_team"]) + pct_complete = here / needed + + $("#prog-bar").width (pct_complete * 100) + "%" + $("#players-needed").text here + " " + ((if here is 1 then "player has" else "players have")) + " signed up. " + needed + " players needed. " + + # Update the user list + players = "" + for player in tournament["players"] + players = players + "<li>"+player["user_name"]+"</li>" + $("#tournament-users").html players + + # Enable/disable the "start" button depending on the number of players + $("input[value=\"Start Tournament\"]").prop "disabled", (if (pct_complete >= 1) then false else true) + + # Switch views if the tournament has been started + if tournament["status"] is 1 + window.location.reload true + + # Do it all again + setTimeout (-> + $.ajax(url: json_url).done update + return + ), 2000 + +# Now kick off the whole process +window.onload = -> + $.ajax(url: json_url).done update diff --git a/app/assets/javascripts/users/edit.js.coffee b/app/assets/javascripts/users/edit.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/users/edit.js.coffee diff --git a/app/assets/javascripts/users/index.js.coffee b/app/assets/javascripts/users/index.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/users/index.js.coffee diff --git a/app/assets/javascripts/users/new.js.coffee b/app/assets/javascripts/users/new.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/users/new.js.coffee diff --git a/app/assets/javascripts/users/show.js.coffee b/app/assets/javascripts/users/show.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/users/show.js.coffee diff --git a/app/assets/stylesheets/alerts.css.scss b/app/assets/stylesheets/alerts.css.scss deleted file mode 100644 index c01a620..0000000 --- a/app/assets/stylesheets/alerts.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the alerts controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css deleted file mode 100644 index 3192ec8..0000000 --- a/app/assets/stylesheets/application.css +++ /dev/null @@ -1,13 +0,0 @@ -/* - * This is a manifest file that'll be compiled into application.css, which will include all the files - * listed below. - * - * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, - * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. - * - * You're free to add application-wide styles to this file and they'll appear at the top of the - * compiled file, but it's generally better to create a new file per style scope. - * - *= require_self - *= require_tree . - */ diff --git a/app/assets/stylesheets/application.css.less b/app/assets/stylesheets/application.css.less new file mode 100644 index 0000000..c6020bd --- /dev/null +++ b/app/assets/stylesheets/application.css.less @@ -0,0 +1,8 @@ +/* + * This is a manifest file that'll be compiled into application.css. + */ +/* If you put any styles in this file directly, I will knife you. */ + +@import "colors"; +@import "bootstrapify"; +@import "scaffolds"; diff --git a/app/assets/stylesheets/bootstrapify.less b/app/assets/stylesheets/bootstrapify.less new file mode 100644 index 0000000..eb3b86b --- /dev/null +++ b/app/assets/stylesheets/bootstrapify.less @@ -0,0 +1,117 @@ +/* Copyright (C) 2014 Andrew Murrell + * Copyright (C) 2014 Davis Webb + * Copyright (C) 2014 Guntas Grewal + * Copyright (C) 2014 Luke Shumaker + * Copyright (C) 2014 Nathaniel Foy + * Copyright (C) 2014 Tomer Kimia + * + * This file is part of Leaguer. + * + * Leaguer is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Leaguer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the Affero GNU General Public License + * along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + */ + +@import "bootstrap"; + +// Connect Bootstrap classes to reasonable defaults + +.btn { + &:extend(.btn-default); +} + +button, +*[role="button"], +input[type="submit"], +input[type="reset"], +input[type="button"] { + &:extend(.btn); +} + +.navbar { + form { + &:extend(.navbar-form); + } + .nav { + &:extend(.navbar-nav); + } + ul { + &:extend(.nav); + } +} +form { + // This list of types is taken from the 2014-04-29 draft of + // the HTML5 spec: + // http://www.w3.org/TR/2014/CR-html5-20140429/forms.html#attr-input-type + input[type="text"], + input[type="search"], + input[type="tel"], + input[type="url"], + input[type="email"], + input[type="password"], + input[type="date"], + input[type="time"], + input[type="number"], + input[type="range"], + input[type="color"], + select, textarea { + &:extend(.form-control); + } + label { + &:extend(.control-label); + } +} + +header nav { + &:extend(.navbar); + &:extend(.navbar-default); +} + +.alert:extend(.alert-default) {} +.alert-success, +.alert-info, +.alert-warning, +.alert-danger { &:extend(.alert); } + +.panel-primary, +.panel-success, +.panel-info, +.panel-warning, +.panel-danger { &:extend(.panel); } + +.panel { + h1, h2, h3, h4, h5, h6 { + &:extend(.panel-heading); + &:extend(.panel-title); + } +} + +// Connect built-in Rails classes to the Bootstrap classes. + +form { + .field { + &:extend(.form-group); + .field_with_errors { + display: inline !important; + } + } + .field_with_errors { + &:extend(.has-error); + } +} + +#error_explanation { + &:extend(.panel-danger); + &:extend(.container); + padding-left: 0; + padding-right: 0; +} diff --git a/app/assets/stylesheets/brackets.css.scss b/app/assets/stylesheets/brackets.css.scss deleted file mode 100644 index 481e6e6..0000000 --- a/app/assets/stylesheets/brackets.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the brackets controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/colors.less b/app/assets/stylesheets/colors.less new file mode 100644 index 0000000..61e1e57 --- /dev/null +++ b/app/assets/stylesheets/colors.less @@ -0,0 +1,32 @@ +/* +@darker-orange: #9D4102; +@link-yellow: #FFC50D; +@orange: #DD9125; +@page-color: #444; +@toolbar-color: black; +*/ + +// See this file for a complete list of variables: +// vendor/bundle/ruby/2.0.0/gems/bootstrap-sass-3.1.1.1/vendor/assets/stylesheets/bootstrap/_variables.less +// Or visit: +// http://getbootstrap.com/customize/#less-variables +/* +@gray-darker: +@gray-dark: +@gray: +@gray-light: +@gray-lighter: + +@brand-primary: +@brand-success: +@brand-info: +@brand-warning: +@brand-danger: +*/ + +/* +@text-color: #DD9125; +@body-bg: #333; +*/ + +@legend-border-color: #ccc; // matches the default @input-border and @btn-default-border diff --git a/app/assets/stylesheets/games.css.scss b/app/assets/stylesheets/games.css.scss deleted file mode 100644 index db1b7bc..0000000 --- a/app/assets/stylesheets/games.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the games controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/main.css.scss b/app/assets/stylesheets/main.css.scss deleted file mode 100644 index a0d94c1..0000000 --- a/app/assets/stylesheets/main.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the main controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/matches.css.scss b/app/assets/stylesheets/matches.css.scss deleted file mode 100644 index 4c396e3..0000000 --- a/app/assets/stylesheets/matches.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the matches controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/pms.css.scss b/app/assets/stylesheets/pms.css.scss deleted file mode 100644 index 5106093..0000000 --- a/app/assets/stylesheets/pms.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the pms controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/scaffolds.css.scss b/app/assets/stylesheets/scaffolds.css.scss deleted file mode 100644 index 6ec6a8f..0000000 --- a/app/assets/stylesheets/scaffolds.css.scss +++ /dev/null @@ -1,69 +0,0 @@ -body { - background-color: #fff; - color: #333; - font-family: verdana, arial, helvetica, sans-serif; - font-size: 13px; - line-height: 18px; -} - -p, ol, ul, td { - font-family: verdana, arial, helvetica, sans-serif; - font-size: 13px; - line-height: 18px; -} - -pre { - background-color: #eee; - padding: 10px; - font-size: 11px; -} - -a { - color: #000; - &:visited { - color: #666; - } - &:hover { - color: #fff; - background-color: #000; - } -} - -div { - &.field, &.actions { - margin-bottom: 10px; - } -} - -#notice { - color: green; -} - -.field_with_errors { - padding: 2px; - background-color: red; - display: table; -} - -#error_explanation { - width: 450px; - border: 2px solid red; - padding: 7px; - padding-bottom: 0; - margin-bottom: 20px; - background-color: #f0f0f0; - h2 { - text-align: left; - font-weight: bold; - padding: 5px 5px 5px 15px; - font-size: 12px; - margin: -7px; - margin-bottom: 0px; - background-color: #c00; - color: #fff; - } - ul li { - font-size: 12px; - list-style: square; - } -} diff --git a/app/assets/stylesheets/scaffolds.less b/app/assets/stylesheets/scaffolds.less new file mode 100644 index 0000000..bbdd989 --- /dev/null +++ b/app/assets/stylesheets/scaffolds.less @@ -0,0 +1,305 @@ +/* Copyright (C) 2014 Andrew Murrell + * Copyright (C) 2014 Davis Webb + * Copyright (C) 2014 Guntas Grewal + * Copyright (C) 2014 Luke Shumaker + * Copyright (C) 2014 Nathaniel Foy + * Copyright (C) 2014 Tomer Kimia + * + * This file is part of Leaguer. + * + * Leaguer is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Leaguer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the Affero GNU General Public License + * along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + */ + +/* Mixins ***********************************************************/ +/* These are just here for other things to @extend. */ +/* Ok, they're really classes, but we don't use them like it. */ + +/* More elegant version of .input-group, which is over-complicated + because it supports all kind of things we don't always need to. + This is useful because it doesn't require extra HTML elements, + where .input-group does. */ +.simple-input-group { + white-space: nowrap; + display: table; + span { + display: table-cell; + } + .btn, .form-control { + display: table-cell !important; + /* .simple-input-group .btn:not(:last-child), + * .simple-input-group .form-control:not(:last-child) { ... } */ + &:not(:last-child) { + @include border-right-radius(0); + } + &:not(:first-child) { + @include border-left-radius(0); + border-left: 0; + } + } +} + +/* IDs **************************************************************/ +/* Specific page elements */ + +#notice { + &:extend(.alert-success); + &:extend(.container); + text-align: center; +} + +#alerts-ajax { + display: none; +} + + +form#search { + &:extend(.navbar-left); + div { + &:extend(.simple-input-group); + } +} + +#user-actions { + &:extend(.navbar-btn); + &:extend(.navbar-right); + &:extend(.simple-input-group); + span { + padding-right: .25em; + } +} + +#error_explanation ul { + list-style: disc outside none; +} + +#players-needed { + text-align: center; + font-style: italic; +} + +#tournament-side-params { + background: none repeat scroll 0 0 rgba(0,0,0,0.5); + border-radius: 5px; + float: right; + font-size: 7px; + padding: 10px; + + p { + font-size: 10px; + margin-bottom: 5px; + } + +} + +#peer_review_boxes li { + cursor: move; + position: relative; + float: left; + margin: 5px; + width: 180px; + height: 240px; + border: 1px solid rgb(0, 0, 0); + text-align: center; + padding-top: 10px; + background-color: rgb(238, 238, 255); +} + +// Nothing uses this one right now +#peer_review_numeric li { + cursor: move; + position: relative; + float: left; + margin: 5px; + width: 180px; + height: 240px; + border: 1px solid rgb(0, 0, 0); + text-align: center; + padding-top: 10px; + background-color: rgb(238, 238, 255); +} + +/**** INDEX PAGE - TABLE AND GRAPH ****/ +#matches-table { + &:extend(.table); + color: #FFF; + + form { + color: #333; + } +} + +/* Classes **********************************************************/ + +/* +.btn { + &.user:extend(.btn-info) {} + &.signup:extend(.btn-success) {} + &.signin:extend(.btn-warning) {} + &.signout:extend(.btn-danger) {} + &.server:extend(.btn-warning) {} + &.create-alert { + color: white; + background-color: rgb(255, 69, 0); + border-color: rgb(255, 69, 0); + } + &.alerts { + color: white; + background-color: hsl(0, 69%, 22%); + } +} +*/ + +form.button_to, form.button_to div { + display: inline; +} + +div.field span.help-block { + float: right; + margin-top: 0; + margin-bottom: 0; +} + +/* Elements *********************************************************/ + +footer { + &:extend(.container); + margin-top: 1em; + border-top: solid 1px $hr-border; + text-align: center; +} + +fieldset { + border: solid 1px $legend-border-color; + border-radius: .5em; + padding: 1em; + margin: 1em 0; + legend { + display: block; + margin: 0; + padding: .25em .5em; + width: auto; + border: solid 1px $legend-border-color; + border-radius: .5em; + } +} + +form ul { + list-style: none; +} + +pre { + text-align: left; +} + +/* Misc *************************************************************/ + +.tournament-listing { + margin: 10px 0px; + border-radius: 5px; + box-shadow: 0px 0px 3px #B8B8B8; + background-color: rgba(0, 0, 0, 0.6); + border: 1px solid #AAAAAA; + min-height: 100px; + padding: 8px 4px; + + div.row { + margin-left: 2%; + } + p.message { + margin-top: 10px; + } + + /* AKA the listing title */ + h3 { + margin-top: 0px; + color: #F0AD4E; + font-weight: bold; + } + + h3:hover { + color: #D09D3E; + } + + /* host of the tournament */ + .host { + font-weight: bold; + color: #FFF; + } + + .col-md-8 { + padding: 0; + a { + padding: 5px 0 0 0; + } + } + + .t-game{ + font-weight: bold; + text-align: center; + } + + .t-image{ + display: block; + margin:auto; + } +} + + +.user-listing { + margin: 10px 0px; + border-radius: 5px; + box-shadow: 0px 0px 3px #B8B8B8; + background-color: rgba(0, 0, 0, 0.6); + border: 1px solid #AAAAAA; + min-height: 100px; + padding: 8px 4px; + display: inline-table; + + /* AKA the listing title */ + h3 { + margin-top: 0px; + color: #F0AD4E; + font-weight: bold; + } + + h3:hover { + color: #D09D3E; + } + + .things { + padding: 0px 10px; + } + + p { + margin: 0; + } + div.row { + margin-left: 2%; + } +} + +// Limitation: Only one box can be expanded at a time +.collapsible { + .collapsed { display: block; } + .expanded { display: none; } + &:target { + .collapsed { display: none; } + .expanded { display: block; } + } +} + +.simple_captcha { + background-color: rgba(255, 255, 255, 0.7); + margin: 10px 0px; +} diff --git a/app/assets/stylesheets/search.css.scss b/app/assets/stylesheets/search.css.scss deleted file mode 100644 index 22fd394..0000000 --- a/app/assets/stylesheets/search.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the search controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/servers.css.scss b/app/assets/stylesheets/servers.css.scss deleted file mode 100644 index 4710386..0000000 --- a/app/assets/stylesheets/servers.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the servers controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/sessions.css.scss b/app/assets/stylesheets/sessions.css.scss deleted file mode 100644 index 7bef9cf..0000000 --- a/app/assets/stylesheets/sessions.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the sessions controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/teams.css.scss b/app/assets/stylesheets/teams.css.scss deleted file mode 100644 index 320d00d..0000000 --- a/app/assets/stylesheets/teams.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the teams controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/tournaments.css.scss b/app/assets/stylesheets/tournaments.css.scss deleted file mode 100644 index e372b90..0000000 --- a/app/assets/stylesheets/tournaments.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the tournaments controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/users.css.scss b/app/assets/stylesheets/users.css.scss deleted file mode 100644 index 1efc835..0000000 --- a/app/assets/stylesheets/users.css.scss +++ /dev/null @@ -1,3 +0,0 @@ -// Place all the styles related to the users controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/alerts_controller.rb b/app/controllers/alerts_controller.rb index a3cb8f9..1da2c9b 100644 --- a/app/controllers/alerts_controller.rb +++ b/app/controllers/alerts_controller.rb @@ -1,6 +1,26 @@ -class AlertsController < ApplicationController - before_action :set_alert, only: [:show, :edit, :update, :destroy] +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +class AlertsController < ApplicationController # GET /alerts # GET /alerts.json def index @@ -25,6 +45,13 @@ class AlertsController < ApplicationController # POST /alerts.json def create @alert = Alert.new(alert_params) + @alert.author = current_user + users = {} + users = User.all + + for i in 0..users.length + current_user.send_message(users[i], @alert.message, "Pay Attention!") + end respond_to do |format| if @alert.save @@ -62,6 +89,7 @@ class AlertsController < ApplicationController end private + # Use callbacks to share common setup or constraints between actions. def set_alert @alert = Alert.find(params[:id]) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 27ef6a7..f45eb49 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,5 +1,72 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class ApplicationController < ActionController::Base + before_action :set_object, only: [:show] + before_action :check_create, only: [:new, :create] + before_action :check_edit, only: [:edit, :update] + before_action :check_delete, only: [:destroy] + # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception + + #include sessionhelper for the session controller and view + include SessionsHelper + + include SimpleCaptcha::ControllerHelpers + + def check_permission(verb, object=nil) + unless current_user.can?("#{verb.to_s}_#{noun}".to_sym) or object.try(:check_permission, current_user, verb) + respond_to do |format| + format.html do + if object.nil? + redirect_to send(noun.pluralize+"_url"), notice: "You don't have permission to #{verb} #{noun.pluralize}." + else + redirect_to object, notice: "You don't have permission to #{verb} this #{noun}." + end + end + format.json { render json: "Permission denied", status: :forbidden } + end + end + end + + def noun + @noun ||= self.class.name.underscore.sub(/_controller$/, '').singularize + end + + def set_object + object = send("set_"+noun) + end + + def check_create + check_permission(:create) + end + def check_edit + object = send("set_"+noun) + check_permission(:edit, object) + end + def check_delete + object = send("set_"+noun) + check_permission(:edit, object) + end end diff --git a/app/controllers/brackets_controller.rb b/app/controllers/brackets_controller.rb index fe43ca9..014687f 100644 --- a/app/controllers/brackets_controller.rb +++ b/app/controllers/brackets_controller.rb @@ -1,20 +1,52 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class BracketsController < ApplicationController - before_action :set_bracket, only: [:show, :edit, :update, :destroy] + before_action :set_tournament, only: [:index, :create] # GET /brackets # GET /brackets.json def index - @brackets = Bracket.all + @tournament = Tournament.find(params[:tournament_id]) + @brackets = @tournament.brackets end # GET /brackets/1 # GET /brackets/1.json def show - end + @results = (@tournament.status == 4)? @bracket.calcResult : nil; + @matches = @tournament.stages.order(:id).first.matches_ordered + @numTeams = @tournament.min_teams_per_match + @logBase = @numTeams + + # depth of SVG tree + @depth = Math.log(@matches.count*(@logBase-1),@logBase).floor+1; + + # height of SVG + @matchHeight = 50*@logBase; + @height = [(@matchHeight+50) * @logBase**(@depth-1) + 100, 500].max; - # GET /brackets/new - def new - @bracket = Bracket.new + @base = 1 + @pBase = 1 end # GET /brackets/1/edit @@ -24,14 +56,17 @@ class BracketsController < ApplicationController # POST /brackets # POST /brackets.json def create - @bracket = Bracket.new(bracket_params) + @bracket = @tournament.brackets.build(user: current_user) + @bracket.name = current_user.user_name + "'s Prediction for " + @tournament.name respond_to do |format| - if @bracket.save + if @tournament.status == 1 && @tournament.stages.first.scheduling_method == "elimination" && @tournament.stages.first.matches.first.status < 2 + @bracket.save + @bracket.create_matches format.html { redirect_to @bracket, notice: 'Bracket was successfully created.' } - format.json { render action: 'show', status: :created, location: @bracket } + format.json { render action: 'edit', status: :created, location: @bracket } else - format.html { render action: 'new' } + format.html { redirect_to tournaments_path action: 'You can\'t make a bracket for this tournament' } format.json { render json: @bracket.errors, status: :unprocessable_entity } end end @@ -41,11 +76,11 @@ class BracketsController < ApplicationController # PATCH/PUT /brackets/1.json def update respond_to do |format| - if @bracket.update(bracket_params) - format.html { redirect_to @bracket, notice: 'Bracket was successfully updated.' } + if @bracket.predict_winners(prediction_params) + format.html { redirect_to @tournament, notice: 'Your bracket was made! Check back when this stage finishes to see how you did!' } format.json { head :no_content } else - format.html { render action: 'edit' } + format.html { redirect_to @tournament, notice: 'bracket was not made... :('} format.json { render json: @bracket.errors, status: :unprocessable_entity } end end @@ -64,11 +99,24 @@ class BracketsController < ApplicationController private # Use callbacks to share common setup or constraints between actions. def set_bracket + @tournament = Tournament.find(params[:tournament_id]) @bracket = Bracket.find(params[:id]) end + def set_tournament + @tournament = Tournament.find(params[:tournament_id]) + end + # Never trust parameters from the scary internet, only allow the white list through. def bracket_params + # bracket[user_id] + # bracket[tournament_id] + # bracket[name] + # bracket[matches][#{i}] params.require(:bracket).permit(:user_id, :tournament_id, :name) end + + def prediction_params + params.require(:bracket).require(:matches) + end end diff --git a/app/controllers/games_controller.rb b/app/controllers/games_controller.rb index 27df771..b724d3a 100644 --- a/app/controllers/games_controller.rb +++ b/app/controllers/games_controller.rb @@ -1,6 +1,26 @@ -class GamesController < ApplicationController - before_action :set_game, only: [:show, :edit, :update, :destroy] +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +class GamesController < ApplicationController # GET /games # GET /games.json def index diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb index 6519d7b..0ba4d94 100644 --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -1,2 +1,4 @@ class MainController < ApplicationController + def homepage + end end diff --git a/app/controllers/matches_controller.rb b/app/controllers/matches_controller.rb index 4042d3c..70d7359 100644 --- a/app/controllers/matches_controller.rb +++ b/app/controllers/matches_controller.rb @@ -1,62 +1,92 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class MatchesController < ApplicationController - before_action :set_match, only: [:show, :edit, :update, :destroy] + require 'httparty' + require 'json' + require 'delayed_job' + + before_action :set_tournament, only: [:index] - # GET /matches - # GET /matches.json + # GET /tournaments/1/matches + # GET /tournaments/1/matches.json def index - @matches = Match.all end - # GET /matches/1 - # GET /matches/1.json + # GET /tournaments/1/matches/1 + # GET /tournaments/1/matches/1.json def show end - # GET /matches/new - def new - @match = Match.new - end - - # GET /matches/1/edit - def edit - end - - # POST /matches - # POST /matches.json - def create - @match = Match.new(match_params) - - respond_to do |format| - if @match.save - format.html { redirect_to @match, notice: 'Match was successfully created.' } - format.json { render action: 'show', status: :created, location: @match } - else - format.html { render action: 'new' } - format.json { render json: @match.errors, status: :unprocessable_entity } - end - end - end - - # PATCH/PUT /matches/1 - # PATCH/PUT /matches/1.json + # PATCH/PUT /tournaments/1/matches/1 + # PATCH/PUT /tournaments/1/matches/1.json def update - respond_to do |format| - if @match.update(match_params) - format.html { redirect_to @match, notice: 'Match was successfully updated.' } - format.json { head :no_content } - else - format.html { render action: 'edit' } - format.json { render json: @match.errors, status: :unprocessable_entity } + notice = nil + case @match.status + when 0 + # Created, waiting to be scheduled + when 1 + # Scheduled, waiting to start + if (@tournament.hosts.include? current_user) and (params[:update_action] == "start") + @match.status = 2 + @match.start_sampling + if @match.save + notice = 'Match has started.' + else + respond_to do |format| + format.html { render action: 'show' } + format.json { render json: @match.errors, status: :unprocessable_entity } + end + return + end + end + when 2 + # Started, waiting to finish + @match.handle_sampling(current_user, params) + # The @match.status will be updated by Statistic's after_save hook + if @match.status == 3 + notice = 'Match has finished' + end + when 3 + if (@tournament.hosts.include? current_user) and (params[:update_action] == "start") + ok = true + ActiveRecord::Base.transaction do + ok &= @match.statistics.destroy_all + ok &- @match.status = 1 + ok &= @match.save + end + if ok + notice = "Match has been reset" + else + respond_to do |format| + format.html { render action: 'show' } + format.json { render json: @match.errors, status: :unprocessable_entity } + end + return + end end end - end - - # DELETE /matches/1 - # DELETE /matches/1.json - def destroy - @match.destroy respond_to do |format| - format.html { redirect_to matches_url } + format.html { redirect_to match_path(@match), notice: notice } format.json { head :no_content } end end @@ -65,10 +95,21 @@ class MatchesController < ApplicationController # Use callbacks to share common setup or constraints between actions. def set_match @match = Match.find(params[:id]) + @tournament = @match.tournament_stage.tournament + end + + def set_tournament + @tournament = Tournament.find(params[:tournament_id]) end # Never trust parameters from the scary internet, only allow the white list through. def match_params params.require(:match).permit(:status, :tournament_stage_id, :winner_id) + params.require(:match).permit(:status, :tournament_stage_id, :winner_id) + end + + # Turn of check_edit, since our #update is flexible + def check_edit + set_match end end diff --git a/app/controllers/pms_controller.rb b/app/controllers/pms_controller.rb index 11f51c8..faaa38f 100644 --- a/app/controllers/pms_controller.rb +++ b/app/controllers/pms_controller.rb @@ -1,6 +1,26 @@ -class PmsController < ApplicationController - before_action :set_pm, only: [:show, :edit, :update, :destroy] +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +class PmsController < ApplicationController # GET /pms # GET /pms.json def index @@ -25,6 +45,10 @@ class PmsController < ApplicationController # POST /pms.json def create @pm = Pm.new(pm_params) + @pm.author = current_user + @pm.recipient = User.find_by_user_name(pm_params['recipient_id']) + + @pm.conversation = @pm.author.send_message(@pm.recipient, @pm.message, @pm.subject).conversation respond_to do |format| if @pm.save @@ -37,6 +61,10 @@ class PmsController < ApplicationController end end + #def reply + # current_user.reply_to_conversation(conversation, message) + #end + # PATCH/PUT /pms/1 # PATCH/PUT /pms/1.json def update @@ -49,6 +77,7 @@ class PmsController < ApplicationController format.json { render json: @pm.errors, status: :unprocessable_entity } end end + current_user.reply_to_conversation(@pm.conversation, @pm.message) end # DELETE /pms/1 diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index ee61487..a66fb40 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -1,2 +1,63 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class SearchController < ApplicationController + + def go + @games = Game.all + @query = params[:query] + @gametype = params[:game_type] + + if ( @gametype.nil? and (@query.nil? or @query.empty?)) then + return + end + + tour_filters = [] + user_filters = [] + unless @query.empty? + tour_filters.push(["name LIKE ?", "%#{@query}%"]) + user_filters.push(["name LIKE ?", "%#{@query}%"]) + end + unless @gametype.nil? or @gametype.empty? + tour_filters.push(["game_id = ?", @gametype]) + end + + if tour_filters.empty? + @tournamets = [] + else + @tournaments = Tournament + tour_filters.each do |filter| + @tournaments = @tournaments.where(*filter) + end + end + + if user_filters.empty? + @players = [] + else + @players = User + user_filters.each do |filter| + @players = @players.where(*filter) + end + end + end + end diff --git a/app/controllers/servers_controller.rb b/app/controllers/servers_controller.rb index 4c12c7e..2a2ce5f 100644 --- a/app/controllers/servers_controller.rb +++ b/app/controllers/servers_controller.rb @@ -1,48 +1,41 @@ -class ServersController < ApplicationController - before_action :set_server, only: [:show, :edit, :update, :destroy] - - # GET /servers - # GET /servers.json - def index - @servers = Server.all - end +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. - # GET /servers/1 - # GET /servers/1.json +class ServersController < ApplicationController + # GET /server + # GET /server.json def show end - # GET /servers/new - def new - @server = Server.new - end - - # GET /servers/1/edit + # GET /server/edit def edit end - # POST /servers - # POST /servers.json - def create - @server = Server.new(server_params) - - respond_to do |format| - if @server.save - format.html { redirect_to @server, notice: 'Server was successfully created.' } - format.json { render action: 'show', status: :created, location: @server } - else - format.html { render action: 'new' } - format.json { render json: @server.errors, status: :unprocessable_entity } - end - end - end - - # PATCH/PUT /servers/1 - # PATCH/PUT /servers/1.json + # PATCH/PUT /server + # PATCH/PUT /server.json def update respond_to do |format| if @server.update(server_params) - format.html { redirect_to @server, notice: 'Server was successfully updated.' } + format.html { redirect_to edit_server_url, notice: 'Server was successfully updated.' } format.json { head :no_content } else format.html { render action: 'edit' } @@ -51,24 +44,15 @@ class ServersController < ApplicationController end end - # DELETE /servers/1 - # DELETE /servers/1.json - def destroy - @server.destroy - respond_to do |format| - format.html { redirect_to servers_url } - format.json { head :no_content } - end - end - private + # Use callbacks to share common setup or constraints between actions. def set_server - @server = Server.find(params[:id]) + @server = Server.first end # Never trust parameters from the scary internet, only allow the white list through. def server_params - params.require(:server).permit(:default_user_permissions) + params.require(:server).permit(:default_user_permissions, :default_user_abilities => User.permission_bits.keys) end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index b035ea0..ead53f0 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,52 +1,47 @@ -class SessionsController < ApplicationController - before_action :set_session, only: [:show, :edit, :update, :destroy] - - # GET /sessions - # GET /sessions.json - def index - @sessions = Session.all - end +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. - # GET /sessions/1 - # GET /sessions/1.json - def show - end +class SessionsController < ApplicationController # GET /sessions/new def new - @session = Session.new - end - - # GET /sessions/1/edit - def edit end # POST /sessions # POST /sessions.json def create - @session = Session.new(session_params) + # find the user... + user = User.find_by_email(params[:username_or_email].to_s) || User.find_by_user_name(params[:username_or_email].to_s) + #@session = Session.new(@user) + # ... and create a new session respond_to do |format| - if @session.save - format.html { redirect_to @session, notice: 'Session was successfully created.' } - format.json { render action: 'show', status: :created, location: @session } + if user && user.authenticate(params[:password].to_s) + sign_in user + format.html { redirect_to root_path, notice: "Welcome, #{user.name}" } # TODO; previous URL + #format.json { # TODO } else format.html { render action: 'new' } - format.json { render json: @session.errors, status: :unprocessable_entity } - end - end - end - - # PATCH/PUT /sessions/1 - # PATCH/PUT /sessions/1.json - def update - respond_to do |format| - if @session.update(session_params) - format.html { redirect_to @session, notice: 'Session was successfully updated.' } - format.json { head :no_content } - else - format.html { render action: 'edit' } - format.json { render json: @session.errors, status: :unprocessable_entity } + format.json { render json: user.errors, status: :unprocessable_entity } end end end @@ -54,21 +49,32 @@ class SessionsController < ApplicationController # DELETE /sessions/1 # DELETE /sessions/1.json def destroy - @session.destroy + #@session.destroy + sign_out respond_to do |format| - format.html { redirect_to sessions_url } + format.html { redirect_to root_path } format.json { head :no_content } end end private - # Use callbacks to share common setup or constraints between actions. - def set_session - @session = Session.find(params[:id]) + + # Only allow creating a session if not logged in. + def check_create + unless current_user.nil? + respond_to do |format| + format.html { redirect_to root_path, notice: "You are already logged in" } # TODO: previous URL + format.json { render json: {"errors" => ["already logged in"]}, status: :forbidden } + end + end end - # Never trust parameters from the scary internet, only allow the white list through. - def session_params - params.require(:session).permit(:user_id, :token) + def check_delete + unless signed_in? + respond_to do |format| + format.html { redirect_to root_path, notice: "You are not logged in" } # TODO: previous URL + format.json { render json: {"errors" => ["not logged in"]}, status: :forbidden } + end + end end end diff --git a/app/controllers/teams_controller.rb b/app/controllers/teams_controller.rb index 57b3d91..9dbdd82 100644 --- a/app/controllers/teams_controller.rb +++ b/app/controllers/teams_controller.rb @@ -1,5 +1,26 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class TeamsController < ApplicationController - before_action :set_team, only: [:show, :edit, :update, :destroy] # GET /teams # GET /teams.json diff --git a/app/controllers/tournaments_controller.rb b/app/controllers/tournaments_controller.rb index 51229cb..8458ba1 100644 --- a/app/controllers/tournaments_controller.rb +++ b/app/controllers/tournaments_controller.rb @@ -1,5 +1,26 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class TournamentsController < ApplicationController - before_action :set_tournament, only: [:show, :edit, :update, :destroy] # GET /tournaments # GET /tournaments.json @@ -10,24 +31,73 @@ class TournamentsController < ApplicationController # GET /tournaments/1 # GET /tournaments/1.json def show + respond_to do |format| + format.html { + case @tournament.status + when 0 + render action: 'show' + when 1 + redirect_to tournament_matches_path(@tournament) + when 2 + redirect_to tournaments_page + end + } + format.json { + data = JSON.parse(@tournament.to_json) + data["players"] = @tournament.players; + render :json => data.to_json + } + end end # GET /tournaments/new def new - @tournament = Tournament.new + @tournament = Tournament.new(tournament_attribute_params) + if @tournament.game + @tournament.game.settings.each do |game_setting| + @tournament.tournament_settings.build( + name: game_setting.name, + value: game_setting.value, + vartype: game_setting.vartype, + type_opt: game_setting.type_opt, + description: game_setting.description, + display_order: game_setting.display_order) + end + end end # GET /tournaments/1/edit def edit + check_permission(:edit, @tournament) end # POST /tournaments # POST /tournaments.json def create - @tournament = Tournament.new(tournament_params) - + ok = true + begin + ActiveRecord::Base.transaction do + ok &= @tournament = Tournament.new(tournament_attribute_params.merge({hosts: [current_user]})) + ok &= @tournament.update(tournament_setting_params) + for i in 1..(params[:num_stages].to_i) do + begin + ok &= @tournament.stages.build(tournament_stage_params(i)) + rescue ActionController::ParameterMissing => e + ok = false + @tournament.errors.add("stages[#{i}]", "needs to be set") + end + end + ok &= @tournament.save + end + rescue ActiveRecord::RecordNotUnique => e + ok = false + @tournament.errors.add(:name, "must be unique") + rescue => e + ok = false + @tournament.errors.add(:exception, "Unknown error: ``#{e.class.name}'' -- #{e.inspect} -- #{e.methods - Object.new.methods}") + end respond_to do |format| - if @tournament.save + if ok format.html { redirect_to @tournament, notice: 'Tournament was successfully created.' } format.json { render action: 'show', status: :created, location: @tournament } else @@ -40,12 +110,71 @@ class TournamentsController < ApplicationController # PATCH/PUT /tournaments/1 # PATCH/PUT /tournaments/1.json def update - respond_to do |format| - if @tournament.update(tournament_params) - format.html { redirect_to @tournament, notice: 'Tournament was successfully updated.' } - format.json { head :no_content } - else - format.html { render action: 'edit' } + case params[:update_action] + when nil + check_permission(:edit, @tournament) + ok = true + ActiveRecord::Base.transaction do + ok &= @tournament.update(tournament_attribute_params) + ok &= @tournament.update(tournament_setting_params) + end + respond_to do |format| + if ok + format.html { redirect_to @tournament, notice: 'Tournament was successfully updated.' } + format.json { head :no_content } + else + format.html { render action: 'edit' } + format.json { render json: @tournament.errors, status: :unprocessable_entity } + end + end + when "join" + # permission checking for join is done in the Tournament model + respond_to do |format| + if @tournament.join(current_user) + format.html { redirect_to @tournament, notice: 'You have joined this tournament.' } + format.json { head :no_content } + else + format.html { redirect_to @tournament, notice: "You can't join this tournament." } + format.json { render json: "Permission denied", status: :forbidden } + end + end + when "leave" + respond_to do |format| + if @tournament.leave(current_user) + format.html { redirect_to tournaments_url, notice: 'You have left the tournament.' } + format.json { head :no_content } + else + format.html { redirect_to @tournament, notice: 'You were\'t a part of this tournament.' } + format.json { render json: "Permission denied", status: :forbidden } + end + end + when "start" + check_permission(:edit, @tournament) + respond_to do |format| + if @tournament.status == 0 + @tournament.status = 1 + @tournament.save + success = true + ActiveRecord::Base.transaction do + # sched = tournament_attribute_params[:type_opt] + # success &= @tournament.stages.create(scheduling_method: sched) + success &= @tournament.stages.first.create_matches + end + if success + format.html { redirect_to @tournament, notice: 'You have started this tournament.' } + format.json { head :no_content } + else + format.html { redirect_to @tournament, notice: "You don't have permission to start this tournament." } + format.json { render json: "Permission denied", status: :forbidden } + end + else + format.html { redirect_to @tournament, notice: "This tournament is not in a state that it can be started." } + format.json { render json: "Permission denied", status: :forbidden } + end + end + else + respond_to do |format| + format.html { redirect_to @tournament, notice: "Invalid action", status: :unprocessable_entity } format.json { render json: @tournament.errors, status: :unprocessable_entity } end end @@ -64,11 +193,48 @@ class TournamentsController < ApplicationController private # Use callbacks to share common setup or constraints between actions. def set_tournament - @tournament = Tournament.find(params[:id]) + begin + @tournament = Tournament.find(params[:id]) + rescue + redirect_to tournaments_url, notice: 'That tournament no longer exists.' + end end # Never trust parameters from the scary internet, only allow the white list through. - def tournament_params - params.require(:tournament).permit(:game_id, :status, :name, :min_players_per_team, :max_players_per_team, :min_teams_per_match, :max_teams_per_match, :scoring_method) + def tournament_attribute_params + params[:num_stages] ||= 1 + if params[:tournament] + p = params.require(:tournament).permit(:game_id, :status, :name, :min_players_per_team, :max_players_per_team, :min_teams_per_match, :max_teams_per_match, :scoring_method) + if p[:game_id] + game = Game.find(p[:game_id]) + p[:min_players_per_team] ||= game.min_players_per_team + p[:max_players_per_team] ||= game.max_players_per_team + p[:min_teams_per_match] ||= game.min_teams_per_match + p[:max_teams_per_match] ||= game.max_teams_per_match + p[:scoring_method] ||= game.scoring_method + end + return p + else + return {} + end end + + def tournament_setting_params + if tournament_attribute_params[:game_id] + game = Game.find(params[:tournament][:game_id]) + params.require(:tournament).permit({:settings => game.settings.collect{|s| s.name}}) + else + return {} + end + end + + def tournament_stage_params(i) + params.require(:tournament).require(:stages).require(i.to_s).permit(:scheduling_method, :seeding_method) + end + + # Turn of check_edit, since our #update is flexible + def check_edit + set_tournament + end + end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 58bf4c6..37c84ae 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,7 +1,32 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class UsersController < ApplicationController - before_action :set_user, only: [:show, :edit, :update, :destroy] + + require 'httparty' + require 'json' # GET /users + # GET /users.json def index @users = User.all @@ -25,13 +50,26 @@ class UsersController < ApplicationController # POST /users.json def create @user = User.new(user_params) + unless (true) # simple_captcha_valid?) + respond_to do |format| + format.html { render action: 'new', status: :unprocessable_entity } + format.json { render json: @user.errors, status: :unprocessable_entity } + end + return + end respond_to do |format| if @user.save - format.html { redirect_to @user, notice: 'User was successfully created.' } + sign_in @user + if @user.id == 1 + # This is the first user, so give them all the power + @user.permissions = 0x7FFFFFFF + @user.save + end + format.html { redirect_to root_path, notice: 'User was successfully created.' } format.json { render action: 'show', status: :created, location: @user } else - format.html { render action: 'new' } + format.html { render action: 'new', status: :unprocessable_entity } format.json { render json: @user.errors, status: :unprocessable_entity } end end @@ -40,8 +78,17 @@ class UsersController < ApplicationController # PATCH/PUT /users/1 # PATCH/PUT /users/1.json def update + ok = true + if params[:user][:remote_usernames].nil? + ok &= @user.update(user_params) + else + params[:user][:remote_usernames].each do |game_name,user_name| + game = Game.find_by_name(game_name) + Sampling::RiotApi::set_remote_name(@user, game, user_name) + end + end respond_to do |format| - if @user.update(user_params) + if ok format.html { redirect_to @user, notice: 'User was successfully updated.' } format.json { head :no_content } else @@ -61,6 +108,7 @@ class UsersController < ApplicationController end end + private # Use callbacks to share common setup or constraints between actions. def set_user @@ -69,6 +117,10 @@ class UsersController < ApplicationController # Never trust parameters from the scary internet, only allow the white list through. def user_params - params.require(:user).permit(:name, :email, :user_name) + permitted = [ :name, :email, :user_name, :password, :password_confirmation ] + if current_user.can? :edit_permissions + permitted.push(:abilities => User.permission_bits.keys) + end + params.require(:user).permit(permitted) end end diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb index 309f8b2..a0367f6 100644 --- a/app/helpers/sessions_helper.rb +++ b/app/helpers/sessions_helper.rb @@ -1,2 +1,87 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + +require 'user' + module SessionsHelper + def sign_in(user) + session = Session.new(user: user) + raw_token = session.create_token + session.save! + + token = Session.hash_token(raw_token) + cookies.permanent[:remember_token] = { value: raw_token, expires: 20.minutes.from_now.utc } + end + + def current_session + Session.find_by(token: Session.hash_token(cookies[:remember_token])) + end + + # sets the @current_user instance varable to the user corresponding + # to the remember token, but only if @current_user is undefined + # since the remember token is hashed, we need to hash the cookie + # to find match the remember token + def current_user + return (current_session.nil? ? User::NilUser.new : current_session.user) + end + + # checks if someone is currently signed in + def signed_in? + !current_user.nil? + end + + def sign_out + if signed_in? + current_session.destroy + end + cookies.delete(:remember_token) + end + + # This is for anyone that cares about how long a user is signed + # in: + # + # Currently I have a user to be signed in forever unless they + # log out (cookies.permanent....). + # + # If you want to change that, change line 7 to this: + # + # cookies[:remember_token] = { value: remember_token, + # expires: 20.years.from_now.utc } + # + # which will expire the cookie in 20 years from its date of + # creation. + # + # Oddly enough, this line above is equivalent to the: + # + # cookies.permanent + # + # This is just a short cut for this line since most people + # create permanent cookies these days. + # + # Other times are: + # + # 10.weeks.from_now + # + # 5.days.ago + # + # etc... end diff --git a/app/models/alert.rb b/app/models/alert.rb index 0516355..189dfde 100644 --- a/app/models/alert.rb +++ b/app/models/alert.rb @@ -1,3 +1,29 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class Alert < ActiveRecord::Base - belongs_to :author + belongs_to :author, class_name: "User" + + def owned_by?(user) + self.author == user + end end diff --git a/app/models/bracket.rb b/app/models/bracket.rb index e8d9c5a..930015a 100644 --- a/app/models/bracket.rb +++ b/app/models/bracket.rb @@ -1,4 +1,54 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class Bracket < ActiveRecord::Base belongs_to :user belongs_to :tournament + has_many :bracket_matches + + def owned_by?(tuser) + self.user == tuser + end + + def create_matches + tournament.stages.order(:id).first.matches.order(:id).each do |m| + bracket_matches.create(match: m) + end + end + + + def predict_winners(predictions) + (0..bracket_matches.count-1).each do |i| + bracket_matches.order(:match_id)[i].update(predicted_winner: Team.find(predictions[(i+1).to_s])); + end + return true + end + + + def calcResults + results = Array.new + (0..bracket_matches.count-1).each do |i| + results.push(bracket_matches.order(:match_id)[i].predicted_winner == tournament.stages.order(:id).first.matches.order(:id).winner) + end + return results + end end diff --git a/app/models/bracket_match.rb b/app/models/bracket_match.rb index 823bc40..d6e53c6 100644 --- a/app/models/bracket_match.rb +++ b/app/models/bracket_match.rb @@ -1,5 +1,31 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class BracketMatch < ActiveRecord::Base belongs_to :bracket belongs_to :match - belongs_to :predicted_winner + belongs_to :predicted_winner, class_name: "Team" + + def owned_by?(user) + self.bracket.owned_by?(user) + end end diff --git a/app/models/game.rb b/app/models/game.rb index 13520ac..f27f174 100644 --- a/app/models/game.rb +++ b/app/models/game.rb @@ -1,3 +1,67 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class Game < ActiveRecord::Base - belongs_to :parent + belongs_to :parent, class_name: "Game" + + has_many :children, class_name: "Game" + + has_many :game_settings + validates_associated :game_settings + alias_attribute :settings, :game_settings + + validates(:name, + presence: true, + length: {minimum: 5}, + uniqueness: {case_sensitive: true}) + + validates(:min_players_per_team, + presence: true, + numericality: { + only_integer: true, + less_than_or_equal_to: :max_players_per_team, + }) + validates(:max_players_per_team, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: :min_players_per_team, + }) + + validates(:min_teams_per_match, + presence: true, + numericality: { + only_integer: true, + less_than_or_equal_to: :max_teams_per_match, + }) + validates(:max_teams_per_match, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: :min_teams_per_match, + }) + + validate :validate_scoring_method + def validate_scoring_method + (not self.scoring_method.try(:empty?)) and (Tournament.scoring_methods.include? scoring_method) + end end diff --git a/app/models/game_setting.rb b/app/models/game_setting.rb index bff8d97..514b57c 100644 --- a/app/models/game_setting.rb +++ b/app/models/game_setting.rb @@ -1,3 +1,49 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class GameSetting < ActiveRecord::Base belongs_to :game + + alias_attribute :value, :default + + validates(:vartype, presence: true, numericality: {only_integer: true}) + validates(:type_opt, presence: true, if: :needs_type_opt?) + + def needs_type_opt? + [ + GameSetting.types[:pick_one_radio], + GameSetting.types[:pick_one_dropdown], + GameSetting.types[:pick_several], + ].include? self.vartype + end + + def self.types + return { + :text_short => 0, + :text_long => 1, + :pick_one_radio => 2, + :pick_several => 3, + :true_false => 4, + :pick_one_dropdown => 5, + } + end end diff --git a/app/models/match.rb b/app/models/match.rb index b5f539b..27c4fbe 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -1,4 +1,148 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class Match < ActiveRecord::Base belongs_to :tournament_stage - belongs_to :winner + has_many :statistics + has_and_belongs_to_many :teams + + belongs_to :winner, class_name: "Team" + + # status:integer + before_save { self.status ||= 0 } + + # tournament_stage:references + validates_presence_of :tournament_stage + + # winner:references + # not validated + + def owned_by?(user) + self.tournament_stage.owned_by?(user) + end + + ## + # Returns whether or not all the statistics have been collected + # such that the match may be considered finished. + def finished? + ok = true + self.tournament_stage.scoring.stats_needed(self).each do |stat| + self.users.each do |user| + ok &= self.statistics.where(user: user, name: stat).first + end + end + ok + end + + ## + # Returns all players involved in this match (from all teams). + def users + ret = [] + self.teams.each{|t| ret.concat(t.users)} + return ret + end + + ## + # Given a sampling class (a class that implements the interface + # described in `/lib/sampling/README.md`), this returns which + # statistics (in an Array of Strings) an instance of the class + # should collect. + def stats_from(sampling_class) + figure_sampling_methods.map{|stat,klass| (sampling_class==klass) ? stat : nil}.select{|s| not s.nil?} + end + + ## + # Delagates PUT/PATCH HTTP params to the appropriate sampling + # methods. + def handle_sampling(user, params) + method_classes.each do |klass| + klass.new(self).handle_user_interaction(user, params) + end + end + + ## + # Delagates out rendering forms to the appropriate sampling + # methods. + def render_sampling(user) + require 'set' + html = '' + + method_classes.each do |klass| + html += '<div>' + html += klass.new(self).render_user_interaction(user) + html += '</div>' + end + + return html.html_safe + end + + ## + # Calls `Sampling#start` on every sampling method that this match + # uses. + def start_sampling + method_classes.each do |klass| + klass.new(self).start + end + end + + private + def figure_sampling_methods + if @sampling_methods.nil? + data = {} + needed = self.tournament_stage.scoring.stats_needed(self) + methods_names = self.tournament_stage.tournament.sampling_methods + methods_names.each do |method_name| + method_class = "Sampling::#{method_name.camelcase}".constantize + needed.each do |stat| + data[stat] ||= {} + data[stat][method_class] = method_class.can_get?(stat) + end + end + + needed.each do |stat| + max_val = nil + max_pri = 0 + data[stat].each do |method,priority| + if priority > max_pri + max_val = method + max_pri = priority + end + end + data[stat] = max_val + end + @sampling_methods = data + end + return @sampling_methods + end + + def method_classes + if @method_classes.nil? + data = Set.new + figure_sampling_methods.each do |stat,method| + data.add(method) + end + @method_classes = data + end + return @method_classes + end + end diff --git a/app/models/pm.rb b/app/models/pm.rb index 0e60f3e..31a04b7 100644 --- a/app/models/pm.rb +++ b/app/models/pm.rb @@ -1,5 +1,41 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class Pm < ActiveRecord::Base - belongs_to :author - belongs_to :recipient + belongs_to :author, class_name: "User" + belongs_to :recipient, class_name: "User" belongs_to :conversation + + def name + return current_user.name + end + + def owned_by?(user) + self.author == user + end + +=begin + def mailboxer_email(email) + return current_user.email + end +=end end diff --git a/app/models/remote_username.rb b/app/models/remote_username.rb index c477f8a..e952779 100644 --- a/app/models/remote_username.rb +++ b/app/models/remote_username.rb @@ -1,4 +1,42 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class RemoteUsername < ActiveRecord::Base belongs_to :game belongs_to :user -end + + def owned_by?(tuser) + self.user == tuser + end + + def value + begin + return JSON::restore(self.json_value) + rescue + return {} + end + end + + def value=(v) + self.json_value = v.to_json + end +end diff --git a/app/models/server.rb b/app/models/server.rb index 120f0fa..df053ca 100644 --- a/app/models/server.rb +++ b/app/models/server.rb @@ -1,2 +1,60 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class Server < ActiveRecord::Base + def default_user_abilities + @abilities ||= User::Abilities.new(DefaultUser.new(self)) + end + def default_user_abilities=(new) + new.each do |k,v| + if v == "0" + v = false + end + default_user_abilities[k] = v + end + end + class DefaultUser + def initialize(server) + @server = server + end + def can?(action) + bit = User.permission_bits[action] + if bit.nil? + return false + else + return (@server.default_user_permissions & bit != 0) + end + end + def add_ability(action) + bit = User.permission_bits[action.to_sym] + unless bit.nil? + @server.default_user_permissions |= bit + end + end + def remove_ability(action) + bit = User.permission_bits[action.to_sym] + unless bit.nil? + @server.default_user_permissions &= ~ bit + end + end + end end diff --git a/app/models/session.rb b/app/models/session.rb index a5fd26e..a85e4fc 100644 --- a/app/models/session.rb +++ b/app/models/session.rb @@ -1,3 +1,68 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class Session < ActiveRecord::Base belongs_to :user + + def owned_by?(tuser) + self.user == tuser + end + + ## + # Create a random remember token for the user. This will be + # changed every time the user creates a new session. + # + # If you want this value, hang on to it; the raw value is + # discarded afterward. + # + # By changing the cookie every new session, any hijacked sessions + # (where the attacker steals a cookie to sign in as a certain + # user) will expire the next time the user signs back in. + # + # The random string is of length 16 composed of A-Z, a-z, 0-9 + # This is the browser's cookie value. + def create_token() + t = SecureRandom.urlsafe_base64 + self.token = Session.hash_token(t) + t + end + + ## + # Encrypt the remember token. + # This is the encrypted version of the cookie stored on + # the database. + # + # The reasoning for storing a hashed token is so that even if + # the database is compromised, the attacker won't be able to use + # the remember tokens to sign in. + def Session.hash_token(token) + # SHA-1 (Secure Hash Algorithm) is a US engineered hash + # function that produces a 20 byte hash value which typically + # forms a hexadecimal number 40 digits long. + # The reason I am not using the Bcrypt algorithm is because + # SHA-1 is much faster and I will be calling this on + # every page a user accesses. + # + # https://en.wikipedia.org/wiki/SHA-1 + Digest::SHA1.hexdigest(token.to_s) + end end diff --git a/app/models/statistic.rb b/app/models/statistic.rb index 341fd9d..6a79e99 100644 --- a/app/models/statistic.rb +++ b/app/models/statistic.rb @@ -1,4 +1,54 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class Statistic < ActiveRecord::Base belongs_to :user belongs_to :match + + validates(:name, presence: true, length: { minimum: 1 }) + + def value + begin + return JSON::restore(self.json_value) + rescue + return {} + end + end + + def value=(v) + self.json_value = v.to_json + end + + after_save :update_match + def update_match + ActiveRecord::Base.transaction do + if (self.name == "win") and (self.value) + self.match.winner = self.match.teams.find{|t| t.users.include? self.user} + end + if (self.match.status == 2) and (self.match.finished?) + self.match.status = 3 + self.match.tournament_stage.scheduling.finish_match(self.match) + end + self.match.save! + end + end end diff --git a/app/models/team.rb b/app/models/team.rb index fa7ba9e..76ee510 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -1,2 +1,32 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class Team < ActiveRecord::Base + has_and_belongs_to_many :matches + has_and_belongs_to_many :users + + alias_attribute :players, :users + + def owned_by?(user) + self.users.include?(user) + end end diff --git a/app/models/tournament.rb b/app/models/tournament.rb index dcdb8d5..b18e359 100644 --- a/app/models/tournament.rb +++ b/app/models/tournament.rb @@ -1,3 +1,206 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class Tournament < ActiveRecord::Base belongs_to :game + + has_many :tournament_stages + # Don't validate presence of stages; sadly, it seems to break things + #validates_presence_of :tournament_stages + alias_attribute :stages, :tournament_stages + + has_many :brackets + + has_many :tournament_settings + + has_and_belongs_to_many :players, class_name: "User", association_foreign_key: "player_id", join_table: "players_tournaments" + + has_and_belongs_to_many :hosts, class_name: "User", association_foreign_key: "host_id", join_table: "hosts_tournaments" + validates_presence_of :hosts + + validates_presence_of :game + + before_save { self.status ||= 0 } + + validates(:name, + presence: true, + length: {minimum: 5}, + uniqueness: {case_sensitive: true}) + + validates(:min_players_per_team, + presence: true, + numericality: { + only_integer: true, + less_than_or_equal_to: :max_players_per_team, + }) + validates(:max_players_per_team, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: :min_players_per_team, + }) + + validates(:min_teams_per_match, + presence: true, + numericality: { + only_integer: true, + less_than_or_equal_to: :max_teams_per_match, + }) + validates(:max_teams_per_match, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: :min_teams_per_match, + }) + + validate :validate_scoring_method + def validate_scoring_method + (not self.scoring_method.try(:empty?)) and (scoring_methods.include? scoring_method) + end + + def owned_by?(user) + self.hosts.include?(user) + end + + # Settings ################################################################# + + def settings + @settings ||= Settings.new(self) + end + + def settings=(setting) + setting.each do |key, value| + value = false if value == "0" + settings[key] = value + end + end + + class Settings + def initialize(tournament) + @tournament = tournament + end + + def [](setting_name) + tournament_setting = @tournament.tournament_settings.find{|s|s.name==setting_name} + if tournament_setting.nil? + return nil + else + return tournament_setting.value + end + end + + def []=(setting_name, val) + tournament_setting = @tournament.tournament_settings.find{|s|s.name==setting_name} + if tournament_setting.nil? + game_setting = @tournament.game.settings.find_by_name(setting_name) + @tournament.tournament_settings.build(name: setting_name, value: val, + vartype: game_setting.vartype, + type_opt: game_setting.type_opt, + description: game_setting.description, + display_order: game_setting.display_order) + else + tournament_setting.value = val + end + end + + def keys + @tournament.tournament_settings.all.collect { |x| x.name } + end + + def empty?() keys.empty? end + def count() keys.count end + def length() count end + def size() count end + + def method_missing(name, *args) + if name.to_s.ends_with?('=') + self[name.to_s.sub(/=$/, '').to_s] = args.first + else + return self[name.to_s] + end + end + end + + # Joining/Leaving ########################################################## + + def joinable_by?(user) + return (status==0 and user.can?(:join_tournament) and !players.include?(user)) + end + + def join(user) + unless joinable_by?(user) + return false + end + players.push(user) + end + + def leave(user) + if players.include?(user) && status == 0 + players.delete(user) + end + end + + # Configured methods ####################################################### + + def scoring + @scoring ||= "Scoring::#{self.scoring_method.camelcase}".constantize + end + + # Options for configured methods/modules ################################### + # We're conflicted about whether these should be `self.` or not. ########### + + def self.scoring_methods + make_methods "scoring" + end + def scoring_methods + self.class.scoring_methods + end + + def sampling_methods + self.class.make_methods("sampling").select do |name| + "Sampling::#{name.camelcase}".constantize.works_with?(self.game) + end + end + + def self.scheduling_methods + make_methods "scheduling" + end + def scheduling_methods + self.class.scheduling_methods + end + + def self.seeding_methods + make_methods "seeding" + end + def seeding_methods + self.class.seeding_methods + end + + private + def self.make_methods(dir) + @methods ||= {} + if @methods[dir].nil? or Rails.env.development? + @methods[dir] = Dir.glob("#{Rails.root}/lib/#{dir}/*.rb").map{|filename| File.basename(filename, ".rb") } + end + return @methods[dir] + end end diff --git a/app/models/tournament_setting.rb b/app/models/tournament_setting.rb index b3e6ace..cbf607a 100644 --- a/app/models/tournament_setting.rb +++ b/app/models/tournament_setting.rb @@ -1,3 +1,45 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class TournamentSetting < ActiveRecord::Base belongs_to :tournament + + validates(:vartype, presence: true, numericality: {only_integer: true}) + validates(:type_opt, presence: true, if: :needs_type_opt?) + + def owned_by?(user) + self.tournament.owned_by?(user) + end + + def needs_type_opt? + [ + GameSetting.types[:pick_one_radio], + GameSetting.types[:pick_one_dropdown], + GameSetting.types[:pick_several], + ].include? self.vartype + end + + + def self.types + GameSetting.types + end end diff --git a/app/models/tournament_stage.rb b/app/models/tournament_stage.rb index 205c8cc..4695238 100644 --- a/app/models/tournament_stage.rb +++ b/app/models/tournament_stage.rb @@ -1,3 +1,77 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class TournamentStage < ActiveRecord::Base belongs_to :tournament + validates_presence_of :tournament + + has_many :matches + + validates(:scheduling_method, + presence: true, + inclusion: {in: Tournament.new.scheduling_methods}) + + validates(:seeding_method, + presence: true, + inclusion: {in: Tournament.new.seeding_methods}) + + def owned_by?(user) + self.tournament.owned_by?(user) + end + + # A 1-indexed hash of matches + def matches_ordered + h = {} + i = 1 + self.matches.order(:id).each do |m| + h[i] = m + i += 1 + end + return h + end + + def create_matches + scheduling.create_matches + end + + def to_svg(highlight_user) + return scheduling.graph(highlight_user) + end + + def seed + return seeding.seed.pair(matches, players) + end + + # Accessors to the configured methods + + def scoring + @scoring ||= tournament.scoring + end + + def scheduling + @scheduling ||= "Scheduling::#{self.scheduling_method.camelcase}".constantize.new(self) + end + + def seeding + @seeding ||= "Seeding::#{self.seeding_method.camelcase}".constantize + end end diff --git a/app/models/user.rb b/app/models/user.rb index 4a57cf0..effe166 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,2 +1,234 @@ +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + class User < ActiveRecord::Base + def owned_by?(tuser) + self == tuser + end + ################################################################## + # Relationships # + ################################################################## + has_and_belongs_to_many :tournaments_played, class_name: "Tournament", foreign_key: "player_id", join_table: "players_tournaments" + has_and_belongs_to_many :tournaments_hosted, class_name: "Tournament", foreign_key: "host_id", join_table: "hosts_tournaments" + has_and_belongs_to_many :teams + has_many :sessions + has_many :statistics + has_many :remote_usernames + + ################################################################## + # Attributes # + ################################################################## + + # name:string + validates(:name, presence: true, length: { maximum: 50 }) + + # email:string:uniq + before_save { self.email = email.downcase } + validates(:email, + presence: true, + # This regex is taken from http://www.w3.org/TR/html5/forms.html#e-mail-state-%28type=email%29 + format: { with: /\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\z/ }, + uniqueness: { case_sensitive: false }) + + # user_name:string_uniq + validates(:user_name, + presence: true, + length:{maximum: 50}, + format: {with: /\A[a-zA-Z0-9_-]+\z/}, + uniqueness: {case_sensitive: false }) + + # password_digest:string + has_secure_password validations: false # maps :password and :password_confirmation to :password_digest + validates(:password, + length: { minimum: 6 }, + confirmation: true, + unless: Proc.new { |u| u.password.try(:empty?) and not u.password_digest.try(:empty?) }) + + # permissions:integer + before_save { self.permissions ||= Server.first.default_user_permissions } + + ################################################################## + # XXX: hard-coded-ish. It makes me feel dirty. # + ################################################################## + apply_simple_captcha + acts_as_messageable + def mailboxer_email(object) + return nil + end + + ################################################################## + # remote_usernames # + ################################################################## + + def set_remote_username(game, data) + remote = self.remote_usernames.where(:game => game).first + if remote.nil? + self.remote_usernames.create(game: game, value: data) + else + remote.value = data + remote.save + end + end + + def get_remote_username(game) + obj = self.remote_usernames.where(:game => game).first + if obj.nil? + if game.parent.nil? + return nil + else + return get_remote_username(game.parent) + end + else + return obj.value + end + end + + ################################################################## + # Permissions # + ################################################################## + + def self.permission_bits + return { + :create_tournament => (2**1), + :edit_tournament => (2**2), + :join_tournament => (2**3), + :delete_tournament => (2**4), + + :create_game => (2**5), + :edit_game => (2**6), + :delete_game => (2**7), + + :create_user => (2**8), + :edit_user => (2**9), + :delete_user => (2**10), + + :create_alert => (2**11), + :edit_alert => (2**12), + :delete_alert => (2**13), + + :create_pm => (2**14), + :edit_pm => (2**15), + :delete_pm => (2**16), + + :create_session => (2**17), + :delete_session => (2**18), + + :edit_permissions => (2**19), + :edit_server => (2**20), + + :create_bracket => (2**21), + :edit_bracket => (2**22), + :delete_bracket => (2**23) + } + end + + def can?(action) + bit = User.permission_bits[action] + if bit.nil? + return false + else + return (self.permissions & bit != 0) + end + end + + def add_ability(action) + bit = User.permission_bits[action.to_sym] + unless bit.nil? + self.permissions |= bit + end + end + + def remove_ability(action) + bit = User.permission_bits[action.to_sym] + unless bit.nil? + self.permissions &= ~ bit + end + end + + # A representation of the permission bits as a mock-array. + def abilities + @abilities ||= Abilities.new(self) + end + def abilities=(new) + new.each do |k,v| + if v == "0" + v = false + end + abilities[k] = v + end + end + + # A thin array-like wrapper around the permission bits to make it + # easy to modify them using a form. + class Abilities + def initialize(user) +o @user = user + end + def [](ability) + return @user.can?(ability) + end + def []=(ability, val) + if val + @user.add_ability(ability) + else + @user.remove_ability(ability) + end + end + def keys + User.permission_bits.keys + end + def method_missing(name, *args) + if name.to_s.ends_with?('=') + self[name.to_s.sub(/=$/, '').to_sym] = args.first + else + return self[name.to_sym] + end + end + end + + ################################################################## + # Null-object pattern # + ################################################################## + + class NilUser + def nil? + return true + end + def can?(action) + case action + when :create_user + return true + when :create_session + return true + else + return false + end + end + def method_missing(name, *args) + # Throw an error if User doesn't have this method + super unless User.new.respond_to?(name) + # User has this method -- return a blank value + # 'false' if the method ends with '?'; 'nil' otherwise. + name.to_s.ends_with?('?') ? false : nil + end + end end diff --git a/app/views/alerts/_form.html.erb b/app/views/alerts/_form.html.erb index b60eaf2..e7a2444 100644 --- a/app/views/alerts/_form.html.erb +++ b/app/views/alerts/_form.html.erb @@ -10,11 +10,6 @@ </ul> </div> <% end %> - - <div class="field"> - <%= f.label :author_id %><br> - <%= f.text_field :author_id %> - </div> <div class="field"> <%= f.label :message %><br> <%= f.text_area :message %> diff --git a/app/views/alerts/index.html.erb b/app/views/alerts/index.html.erb index 458b951..5695486 100644 --- a/app/views/alerts/index.html.erb +++ b/app/views/alerts/index.html.erb @@ -12,14 +12,18 @@ </thead> <tbody> - <% @alerts.each do |alert| %> - <tr> - <td><%= alert.author %></td> - <td><%= alert.message %></td> - <td><%= link_to 'Show', alert %></td> - <td><%= link_to 'Edit', edit_alert_path(alert) %></td> - <td><%= link_to 'Destroy', alert, method: :delete, data: { confirm: 'Are you sure?' } %></td> - </tr> + <% if !@alerts.nil? %> + <% @alerts.each do |alert| %> + <tr> + <td><%= alert.author.user_name %></td> + <td><%= alert.message %></td> + <td><%= link_to 'Show', alert %></td> + <td><%= link_to 'Edit', edit_alert_path(alert) %></td> + <td><%= button_to 'Destroy', alert, method: :delete, data: { confirm: 'Are you sure?' } %></td> + </tr> + <% end %> + <% else %> + <td><p>There are no alerts!</p></td> <% end %> </tbody> </table> diff --git a/app/views/alerts/new.html.erb b/app/views/alerts/new.html.erb index 6d04589..db5af2d 100644 --- a/app/views/alerts/new.html.erb +++ b/app/views/alerts/new.html.erb @@ -2,4 +2,4 @@ <%= render 'form' %> -<%= link_to 'Back', alerts_path %> +<%= link_to 'See past Alerts', alerts_path %> diff --git a/app/views/alerts/show.html.erb b/app/views/alerts/show.html.erb index eeab7f7..398a10e 100644 --- a/app/views/alerts/show.html.erb +++ b/app/views/alerts/show.html.erb @@ -1,8 +1,6 @@ -<p id="notice"><%= notice %></p> - <p> <strong>Author:</strong> - <%= @alert.author %> + <%= @alert.author.user_name %> </p> <p> diff --git a/app/views/application/.keep b/app/views/application/.keep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/views/application/.keep diff --git a/app/views/brackets/index.html.erb b/app/views/brackets/index.html.erb index 2195d69..9effe37 100644 --- a/app/views/brackets/index.html.erb +++ b/app/views/brackets/index.html.erb @@ -8,7 +8,6 @@ <th>Name</th> <th></th> <th></th> - <th></th> </tr> </thead> @@ -18,9 +17,8 @@ <td><%= bracket.user %></td> <td><%= bracket.tournament %></td> <td><%= bracket.name %></td> - <td><%= link_to 'Show', bracket %></td> - <td><%= link_to 'Edit', edit_bracket_path(bracket) %></td> - <td><%= link_to 'Destroy', bracket, method: :delete, data: { confirm: 'Are you sure?' } %></td> + <td><%= link_to 'Show', tournament_bracket_path(@tournament, bracket) %></td> + <td><%= link_to 'Edit', edit_tournament_bracket_path(@tournament, bracket) %></td> </tr> <% end %> </tbody> @@ -28,4 +26,3 @@ <br> -<%= link_to 'New Bracket', new_bracket_path %> diff --git a/app/views/brackets/new.html.erb b/app/views/brackets/new.html.erb index c379c15..91d0033 100644 --- a/app/views/brackets/new.html.erb +++ b/app/views/brackets/new.html.erb @@ -1,5 +1,3 @@ <h1>New bracket</h1> -<%= render 'form' %> - -<%= link_to 'Back', brackets_path %> +<%= link_to 'Back', tournament_brackets_path %> diff --git a/app/views/brackets/show.html.erb b/app/views/brackets/show.html.erb index 9c7c14b..fc722a6 100644 --- a/app/views/brackets/show.html.erb +++ b/app/views/brackets/show.html.erb @@ -1,19 +1,130 @@ -<p id="notice"><%= notice %></p> +<% +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +%> +<h2><%= @bracket.name %></h2> -<p> - <strong>User:</strong> - <%= @bracket.user %> -</p> + <h4> Make your prediction for the tournament by clicking on the teams you think will win </h4> -<p> - <strong>Tournament:</strong> - <%= @bracket.tournament %> -</p> +<svg id="prediction-svg" version="1.1" baseProfile="full" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + width="100%" height=<%= "#{@height * 0.66}"%>> + <defs> + <radialGradient id="gradMatch" cx="50%" cy="50%" r="80%" fx="50%" fy="50%"> + <stop offset="0%" style="stop-color:#fff; stop-opacity:1" /> + <stop offset="100%" style="stop-color:#ccc;stop-opacity:0" /> + </radialGradient> + </defs> + <script type="text/ecmascript"><![CDATA[ + <%# + This method does two things: + 1) fill out hidden form with id of matchNum + 2) calculate where the next text is going, and place it there + %> + function chooseWinner(matchNum, teamNum, currentBox){ + console.log(matchNum, teamNum); + var id = '#bracket_matches_'+matchNum; + $(id).val(teamNum); -<p> - <strong>Name:</strong> - <%= @bracket.name %> -</p> + if (matchNum != 1) { + var parent = parseFloat(matchNum+<%= @logBase%> -2)/<%=@logBase%>; + var textBox = (parent - Math.floor(parent)) * <%= @logBase %>; + var parent = Math.floor(parent); + var textBox = Math.round(textBox); + var id = "#svg-match-"+parent+"-team-"+textBox; -<%= link_to 'Edit', edit_bracket_path(@bracket) %> | -<%= link_to 'Back', brackets_path %> + console.log(id); + + $(id).text("Team "+teamNum); + $(id).attr("onclick", "chooseWinner("+parent+", "+teamNum+", "+textBox+")"); + } + else + { + console.log("final countdown"); + for(var i = 0; i < 3; i++){ + id = "#svg-match-"+matchNum+"-team-"+i; + $(id).attr("fill", "black"); + } + id = "#svg-match-"+matchNum+"-team-"+currentBox; + $(id).attr("fill", "green"); + $("#bracket-submit").prop('disabled', false); + } + } + ]]> + </script> + + <% (1..@matches.count).each do |i| %> + <% matchDepth = Math.log(i*(@logBase-1), @logBase).floor+1 %> + <% if matchDepth > Math.log(@base*(@logBase-1), @logBase).floor+1 + @pBase = @base + @base = i + end %> + <g id="svg-match-<%= i %>"> + <rect height="<%= rh = 100 / (@logBase**(@depth-1)+1) - 100/@height %>%" + width="<%= rw = 100/(@depth+1) - 5 %>%" + x="<%= rx = 50/(@depth+1) + 100/(@depth+1)*(@depth-matchDepth) %>%" + y="<%= ry = 100/(@logBase**(matchDepth-1)+1) * (i-@base+1) - rh/2 %>%" + fill="url(#gradMatch)" + rx="5px" + stroke="black" + /> + + <% t = 1 + while t <= @numTeams %> + <rect width="<%= rw-5 %>%" height="<%= rh*Float(30)/(@matchHeight) %>%" x="<%= rx + 2.5 %>%" y="<%= ry + (Float(t-1)/@numTeams)*rh + 1 %>%" fill="white" /> + <text id="svg-match-<%= i %>-team-<%= t-1 %>" x="<%= rx + rw/4 %>%" y="<%= ry + (Float(t-1)/@numTeams + Float(33)/(@matchHeight))*rh %>%" font-size="150%" + <% if @matches[i].teams[t-1] && !@results %> + onclick="chooseWinner(<%= @matches[i].id%>, <%= @matches[i].teams[t-1].id %>)" + <% end %> + > + <% if @matches[i].teams[t-1] %> + Team <%= @matches[i].teams[t-1].id %> + <% end %> + </text> + <% if (t < @numTeams) %> + <text x="<%= rx + 1.3*rw/3 %>%" y="<%= ry + (Float(t)/@numTeams)*rh + 1%>%" font-size="150%"> VS </text> + <% end %> + <% t = t + 1 %> + <% end %> + + <% if i > 1 %> + <% parent = (i+@logBase-2)/@logBase + pDepth = Math.log(parent*(@logBase-1), @logBase).floor+1 + lastrx = 50/(@depth+1) + 100/(@depth+1)*(@depth-pDepth) + lastry = 100/(@logBase**(pDepth-1)+1) * (parent-@pBase+1) - rh/2 %> + <line x1="<%= rx+rw %>%" y1="<%= ry+rh/2 %>%" x2="<%= lastrx%>%" y2="<%= lastry+rh/2%>%" stroke="white" stroke-width="2" > + <% end %> + </g> + + <% end %> +</SVG> + + <%= form_tag(tournament_bracket_path(@tournament, @bracket), method: 'put') do %> + <input type="hidden" name="update_action" value="predict"> + <% for i in 1..@matches.length %> + <%= hidden_field_tag("bracket[matches][#{@matches[i].id.to_s}]", value = nil) %> + <% end %> + <%= submit_tag("Submit Prediction", disabled: true, id: "bracket-submit") %> + <% end %> + +<%= link_to 'Back', tournaments_path %> diff --git a/app/views/common/_error_messages.html.erb b/app/views/common/_error_messages.html.erb new file mode 100644 index 0000000..fdeaf65 --- /dev/null +++ b/app/views/common/_error_messages.html.erb @@ -0,0 +1,11 @@ +<%# http://railscasts.com/episodes/211-validations-in-rails-3 %> +<% if target.errors.any? %> +<div id="error_explanation"> + <h2><%= pluralize(target.errors.count, "error") %> prohibited this form from being submitted:</h2> + <ul> + <% target.errors.full_messages.each do |msg| %> + <li><%= msg %></li> + <% end %> + </ul> +</div> +<% end %> diff --git a/app/views/common/_show_tournament.html.erb b/app/views/common/_show_tournament.html.erb new file mode 100644 index 0000000..3d4b066 --- /dev/null +++ b/app/views/common/_show_tournament.html.erb @@ -0,0 +1,70 @@ +<% +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +%> +<div class="row tournament-listing"> + <div class="col-md-2 col-sm-3 col-xs-6"> + <%= image_tag(target.game.name.parameterize.underscore + ".png", class: "t-image") %> + <p class="t-game"><%= Game.find(target.game_id).name %></p> + </div> + <div class="col-md-8 col-sm-7 col-xs-6"> + <%# "header" %> + <%= link_to(target) do %><h3><%= target.name %></h3><% end %> + <div class="row"> + <div class="col-md-4 host"> + <%= image_tag('http://www.gravatar.com/avatar/' + Digest::MD5.hexdigest(target.hosts.first.email) + '?s=45&d=identicon') %> + <%= target.hosts.first.name %>'s tournament + </div> + <div class="col-md-4 things"> + <p>Players per team: <%= target.min_players_per_team %></p> + <p>Players signed up: <%= target.players.count %></p> + </div> + <div class="col-md-4 things"> + <p><%# (target.randomized_teams)? "Teams are Random" : "Teams are Chosen" %></p> + <p>Players signed up: <%= target.players.count %></p> + </div> + </div> + </div> + <div class="col-md-2 col-sm-2 col-xs-2"> + <% if signed_in? %> + <% if !target.players.include?(current_user) && target.status == 0 %> + <%= form_tag(tournament_path(target), method: "put", class: :button_to) do %> + <input type="hidden" name="update_action" value="join"> + <%= submit_tag("Join")%> + <% end %> + <% elsif target.players.include?(current_user)%> + <p class="message">You've signed up for this tournament!</p> + <% end %> + <% @user_bracket = target.brackets.find_by(user: current_user) %> + <% if target.status == 1 && target.stages.order(:id).first.scheduling_method == "elimination" && target.stages.order(:id).first.matches.order(:id).first.status < 2 && !@user_bracket %> + <%= form_tag(tournament_brackets_path(target), method: "post", class: :button_to) do %> + <%= submit_tag("Make Bracket") %> + <% end %> + <% elsif @user_bracket && target.status == 4 %> + <%= form_tag(tournament_bracket_path(@tournament, @bracket), method: 'put', class: :button_to) do %> + <input type="hidden" name="update_action" value="results"> + <%= submit_tag("Bracket Results") %> + <% end %> + <% end %> + <% end %> + </div> +</div> diff --git a/app/views/common/_show_user.html.erb b/app/views/common/_show_user.html.erb new file mode 100644 index 0000000..46a7a77 --- /dev/null +++ b/app/views/common/_show_user.html.erb @@ -0,0 +1,47 @@ +<% +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +%> +<div class="row user-listing"> + <div class="col-md-3 col-sm-4 col-xs-4"><%= image_tag ('http://www.gravatar.com/avatar/' + Digest::MD5.hexdigest(target.email) + '?s=100&d=mm') %></div> + + + <div class="col-md-9 col-sm-8 col-xs-8"> + <%# "header" %> + <%= link_to(target) do %><h3><%= target.user_name %></h3><% end %> + + <div class="row"> + <div class="col-md-6 things"> + <p> Preferred Name: </p> + <p> <%= target.name %></p> + </div> + <div class="col-md-6 things"> + <p>Latest Tournament: + <% if !target.tournaments_played.first.nil? %> + <%= target.tournaments_played.first.name %> + <% end %> + </p> + </div> + </div> + + </div> +</div> diff --git a/app/views/games/index.html.erb b/app/views/games/index.html.erb index 71e59a0..5a1a270 100644 --- a/app/views/games/index.html.erb +++ b/app/views/games/index.html.erb @@ -1,6 +1,6 @@ <h1>Listing games</h1> -<table> +<table class="table table-hover"> <thead> <tr> <th>Parent</th> @@ -28,7 +28,7 @@ <td><%= game.scoring_method %></td> <td><%= link_to 'Show', game %></td> <td><%= link_to 'Edit', edit_game_path(game) %></td> - <td><%= link_to 'Destroy', game, method: :delete, data: { confirm: 'Are you sure?' } %></td> + <td><%= button_to 'Destroy', game, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> <% end %> </tbody> @@ -36,4 +36,6 @@ <br> -<%= link_to 'New Game', new_game_path %> + +<%= link_to 'New Game', new_game_path, {:class => "btn btn-warning"} %> + diff --git a/app/views/games/show.html.erb b/app/views/games/show.html.erb index 28da040..6184784 100644 --- a/app/views/games/show.html.erb +++ b/app/views/games/show.html.erb @@ -1,5 +1,3 @@ -<p id="notice"><%= notice %></p> - <p> <strong>Parent:</strong> <%= @game.parent %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index cefd1be..ae8b679 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,14 +1,95 @@ -<!DOCTYPE html> -<html> +<% +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +%><!DOCTYPE html> +<html lang="en"> <head> - <title>Leaguer</title> - <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %> - <%= javascript_include_tag "application", "data-turbolinks-track" => true %> - <%= csrf_meta_tags %> + <title>Leaguer</title> + + <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %> + + <%= javascript_include_tag "application", "data-turbolinks-track" => true %> + <%= javascript_include_tag "#{params[:controller]}", "data-turbolinks-track" => true %> + <%= javascript_include_tag "#{params[:controller]}/#{params[:action]}", "data-turbolinks-track" => true %> + + <%= csrf_meta_tags %> + <%= yield :head %> </head> <body> +<header> + <nav> + <div class="container-fluid"> + <div class="navbar-header"> + <%= link_to('Leaguer', root_path, class: "navbar-brand") %> + </div> + <div class="navbar-collapse"> + <div id="user-actions"> + <span> + <% if signed_in? %> + <%= link_to current_user.user_name, current_user, :class => "user", :role => :button %> + <%= link_to "Sign out", session_path("current"), method: "delete", :class => "signout", :role => :button %> + <% else %> + <%= link_to "Log in", new_session_path, :class => "signin", :role => :button %> + <%= link_to "Sign up", new_user_path, :class => "signup", :role => :button %> + <% end %> + </span> + <% if current_user.can? :create_alert %> + <span><%= link_to "Create Alert", new_alert_path, :class => "create-alert", :role => :button %></span> + <% end %> + </div> + <% if signed_in? %> + <ul> + <li><%= link_to "Tournaments", tournaments_path %></li> + <li><%= link_to "Messages", pms_path %></li> + <% if current_user.can? :edit_server %> + <li><%= link_to "Server settings", edit_server_path, :class => "server" %></li> + <% end %> + </ul> + <%# If there is an unread alert then I want to be able + to show the notification icon by saying :style => + display:inline. This will be Done by Guntas once + he figures out how to get unread alerts. Psuedo if + alerts.unread > 0 then display else don't.%> + <%= link_to "", alerts_path, :class => "alerts", :id => "alerts-ajax"%> + <% end %> + <%= form_tag("/search", method: :get, :id => "search") do %><div> + <%= text_field_tag(:query, params[:query], type: :search) %> + <%= submit_tag "Search" %> + </div><% end %> + </div> + </div> + </nav> +</header> + +<% if notice %><div id="notice"><p><%= notice %></p></div><% end %> + +<div class="container"> + <%= yield %> +</div> -<%= yield %> +<footer> + <p>Leaguer © 2014, Tomer Kimia, Andrew Murrell, Luke Shumaker, Nathaniel Foy, Davis Webb, and Guntas Grewal</p> + <%= debug(params) if Rails.env.development? %> +</footer> </body> </html> diff --git a/app/views/main/homepage.html.erb b/app/views/main/homepage.html.erb new file mode 100644 index 0000000..81a991d --- /dev/null +++ b/app/views/main/homepage.html.erb @@ -0,0 +1,36 @@ +<% +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +%> +<div class="jumbotron"> + <h1>Welcome to Leaguer</h1> + <p>This is a tournment management system designed to be used for any team sport. Our peer review system ensures that the best players move on to the next round! Try creating a new tournament and having people sign up for it. </p> + <p id="jumbo-buttons"> + <% if !signed_in? %> + <%= link_to 'Log In', new_session_path, :class => "signin", :role => "button" %> + <%= link_to 'Sign Up', new_user_path, :class => "signup", :role => "button" %> + <% else %> + <%= link_to 'Start a Tournament', new_tournament_path, :role => "button" %> + <% end %> + <%= link_to 'See Ongoing Tournaments', tournaments_path, :role => "button" %> + </p> +</div> diff --git a/app/views/matches/_form.html.erb b/app/views/matches/_form.html.erb index 768b655..a045e98 100644 --- a/app/views/matches/_form.html.erb +++ b/app/views/matches/_form.html.erb @@ -1,16 +1,5 @@ -<%= form_for(@match) do |f| %> - <% if @match.errors.any? %> - <div id="error_explanation"> - <h2><%= pluralize(@match.errors.count, "error") %> prohibited this match from being saved:</h2> - - <ul> - <% @match.errors.full_messages.each do |msg| %> - <li><%= msg %></li> - <% end %> - </ul> - </div> - <% end %> - +<%= form_for([@tournament, @tournament.matches.build]) do |f| %> + <div class="field"> <%= f.label :status %><br> <%= f.number_field :status %> diff --git a/app/views/matches/index.html.erb b/app/views/matches/index.html.erb index 85c94f2..eb827f3 100644 --- a/app/views/matches/index.html.erb +++ b/app/views/matches/index.html.erb @@ -1,31 +1,62 @@ -<h1>Listing matches</h1> +<% +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +%> +<h1><%= @tournament.name %> - Matches</h1> -<table> - <thead> - <tr> - <th>Status</th> - <th>Tournament stage</th> - <th>Winner</th> - <th></th> - <th></th> - <th></th> - </tr> - </thead> - - <tbody> - <% @matches.each do |match| %> - <tr> - <td><%= match.status %></td> - <td><%= match.tournament_stage %></td> - <td><%= match.winner %></td> - <td><%= link_to 'Show', match %></td> - <td><%= link_to 'Edit', edit_match_path(match) %></td> - <td><%= link_to 'Destroy', match, method: :delete, data: { confirm: 'Are you sure?' } %></td> - </tr> - <% end %> - </tbody> +<table id="matches-table" class="table"> + <thead> + <tr> + <th>Name</th> + <th>Status</th> + <th>Winner</th> + <th><!-- link to --></th> + <th><!-- start button --></th> + </tr> + </thead> + <tbody> + <% @tournament.stages.order(:id).each do |stage| %> + <% stage.matches_ordered.keys.sort.reverse.each do |match_key| %><tr> + <% match = stage.matches_ordered[match_key] %> + <td><%= "Match #{match.id}" %></td> + <td><%= match.status %></td> + <td><%= (match.winner.nil? ? "-" : "Team #{match.winner.id}") %></td> + <td><%= link_to "See Match", tournament_match_path(@tournament, match) %> + <td> + <% if match.check_permission(current_user, :edit) %> + <%= form_tag(tournament_match_path(@tournament, match), method: "put") do %> + <input type="hidden" name="update_action" value="start"> + <% @startable = (match.status == 1) and (match.teams.count >= @tournament.min_teams_per_match) %> + <%= submit_tag("Start Match", :disabled => ! @startable) %> + <% end %> + <% end %> + </td> + </tr><% end %> + <% end %> + </tbody> </table> <br> -<%= link_to 'New Match', new_match_path %> +<% @tournament.stages.order(:id).each do |stage| %> + <div class="graph"><%= raw stage.to_svg(current_user) %></div> +<% end %> diff --git a/app/views/matches/new.html.erb b/app/views/matches/new.html.erb index bd4c78c..74e7e3a 100644 --- a/app/views/matches/new.html.erb +++ b/app/views/matches/new.html.erb @@ -1,5 +1,3 @@ <h1>New match</h1> <%= render 'form' %> - -<%= link_to 'Back', matches_path %> diff --git a/app/views/matches/show.html.erb b/app/views/matches/show.html.erb index b47a045..5ee4ecb 100644 --- a/app/views/matches/show.html.erb +++ b/app/views/matches/show.html.erb @@ -1,19 +1,108 @@ -<p id="notice"><%= notice %></p> - +<% +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +%> <p> - <strong>Status:</strong> - <%= @match.status %> + <strong>Status:</strong> + <%= @match.status %> </p> - <p> - <strong>Tournament stage:</strong> - <%= @match.tournament_stage %> + <strong>Tournament stage:</strong> + <%= @tournament.stages.order(:id).index(@match.tournament_stage)+1 %> </p> -<p> - <strong>Winner:</strong> - <%= @match.winner %> -</p> +<%# + Match Status 0 => Created, waiting to be scheduled + Match Status 1 => Scheduled, waiting to start + Match Status 2 => Started, waiting to finish + Match Status 3 => Finished + + Four views:- (status is Match status) + A. Pairings, when status is 1 for either Host or Player Or when status is 2 for player + B. A page the host will see if status is 2 OR 3 + C. The Peer review page that the players will see if status is 3. + D. The page everyone will see when status is 4. + + Note: The change of status from 2 to 3 for League of Legends is coming from League Data Pull (RIOT API) +%> + +<div> + <h2>Teams/users</h2> + <ul> + <% if @match.status == 3 + scores = @match.tournament_stage.scoring.score(@match) + end + %> + <% @match.teams.each do |team| %> + <li>Team <%= team.id %><ul> + <% team.users.each do |user| %> + <% if @match.status < 3 %> + <li><%= user.user_name %></li> + <% else %> + <% stats = Statistic.where(user: user, match: @match) %> + <li><%= user.user_name %> - Score: <%= scores[user] %><ul> + <% stats.all.reject{|s|s.name=="score"}.each do |stat| %> + <li><%= stat.name %>: <%= stat.value %></li> + <% end %> + </ul></li> + <% end %> + <% end %> + </ul></li> + <% end %> + </ul> +</div> + +<% unless @match.winner.nil? %> + <p> + <strong>Winner:</strong> + <%= @match.winner.users.collect{|u| u.user_name}.join(", ") %> + </p> +<% end %> -<%= link_to 'Edit', edit_match_path(@match) %> | -<%= link_to 'Back', matches_path %> +<div id="action"> + <%= form_tag(tournament_match_path(@tournament, @match), method: "put") do %> + <% case @match.status %> + <% when 0 %> + <!-- Created, waiting to be scheduled --> + <p>This match has not yet been scheduled.</p> + <% when 1 %> + <!-- Scheduled, waiting to start --> + <% if @tournament.hosts.include? current_user %> + <input type="hidden" name="update_action" value="start"> + <%= submit_tag("Start Match") %> + <% else %> + <p>Match is waiting to start.</p> + <% end %> + <% when 2 %> + <!-- Started, waiting to finish --> + <%= @match.render_sampling(current_user) %> + <% when 3 %> + <!-- Finished --> + <p>This match is finished.</p> + <% if @tournament.hosts.include? current_user %> + <input type="hidden" name="update_action" value="reset"> + <%= submit_tag("Reset Status") %> + <% end %> + <% end %> + <% end %> + <%= link_to "Back to Tournament", @match.tournament_stage.tournament %> +</div> diff --git a/app/views/pms/_form.html.erb b/app/views/pms/_form.html.erb index 80781a5..b329e24 100644 --- a/app/views/pms/_form.html.erb +++ b/app/views/pms/_form.html.erb @@ -12,24 +12,16 @@ <% end %> <div class="field"> - <%= f.label :author_id %><br> - <%= f.text_field :author_id %> - </div> - <div class="field"> <%= f.label :recipient_id %><br> <%= f.text_field :recipient_id %> </div> - <div class="field"> - <%= f.label :message %><br> - <%= f.text_area :message %> - </div> - <div class="field"> + <div class="field"> <%= f.label :subject %><br> - <%= f.text_area :subject %> + <%= f.text_field :subject %> </div> <div class="field"> - <%= f.label :conversation_id %><br> - <%= f.text_field :conversation_id %> + <%= f.label :message %><br> + <%= f.text_area :message %> </div> <div class="actions"> <%= f.submit %> diff --git a/app/views/pms/index.html.erb b/app/views/pms/index.html.erb index b5169f5..159b3f9 100644 --- a/app/views/pms/index.html.erb +++ b/app/views/pms/index.html.erb @@ -1,35 +1,160 @@ -<h1>Listing pms</h1> +<% +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +%> +<h1> Your Conversations </h1> + +<%= link_to 'Start a new conversation', new_pm_path %> +<br> + +<h3>Unread Conversations</h3> +<% conversations = current_user.mailbox.conversations %> <table> - <thead> - <tr> - <th>Author</th> - <th>Recipient</th> - <th>Message</th> - <th>Subject</th> - <th>Conversation</th> - <th></th> - <th></th> - <th></th> - </tr> - </thead> + <col width="150"> + <col width="250"> + <col width="300"> + <tbody> + <%# if conversations.reject { |c| c.is_unread?(current_user) && (c.receipts_for current_user).last.message.sender != current_user }.empty? %> + <tr> + <tr> + <td><b>With</b></td> + <td><b>Subject</b></td> + <td><b>Body</b></td> + </tr> + <% conversations.each do |conversation| %> + <% receipts = conversation.receipts_for current_user %> + <% if conversation.is_unread?(current_user) && receipts.last.message.sender != current_user %> + <% message = receipts.last.message %> + <tr> + <% if conversation.subject == "Pay Attention!" %> + <% conversation.mark_as_read(current_user) %> + <% end %> + <td> + <% people = conversation.participants %> + <% people.each do |person| %> + <% unless person == current_user %> + <%= truncate(person.user_name, length: 20) %> + <% end %> + <% end %> + </td> + <td><%= truncate(conversation.subject, length: 30) %></td> + <td><%= truncate(message.body, length: 42) %></td> + <td><%= link_to 'View', @pms.find_by(conversation: conversation) %></td> + </tr> + <% end %> + <% end %> + </tr> + <%# else %> + + <%# end %> + </tbody> +</table> +<br> +<h3>Read Conversations</h3> +<% conversations = current_user.mailbox.conversations %> + +<table> + <col width="150"> + <col width="250"> + <col width="300"> <tbody> - <% @pms.each do |pm| %> + <%# if conversations.reject { |c| c.is_read?(current_user) || (c.receipts_for current_user).last.message.sender == current_user }.empty? %> <tr> - <td><%= pm.author %></td> - <td><%= pm.recipient %></td> - <td><%= pm.message %></td> - <td><%= pm.subject %></td> - <td><%= pm.conversation %></td> - <td><%= link_to 'Show', pm %></td> - <td><%= link_to 'Edit', edit_pm_path(pm) %></td> - <td><%= link_to 'Destroy', pm, method: :delete, data: { confirm: 'Are you sure?' } %></td> + <tr> + <td><b>With</b></td> + <td><b>Subject</b></td> + <td><b>Body</b></td> + </tr> + <% conversations.each do |conversation| %> + <% receipts = conversation.receipts_for current_user %> + <% if conversation.is_read?(current_user) || receipts.last.message.sender == current_user %> + <% message = receipts.last.message %> + <tr> + <% if conversation.subject != "Pay Attention!" %> + <td> + <% people = conversation.participants %> + <% people.each do |person| %> + <% unless person == current_user %> + <%= truncate(person.user_name, length: 20) %> + <% end %> + <% end %> + </td> + <td><%= truncate(conversation.subject, length: 30) %></td> + <td><%= truncate(message.body, length: 42) %></td> + <td><%= link_to 'View', @pms.find_by(conversation: conversation) %></td> + <% end %> + </tr> + <% end %> + <% end %> </tr> - <% end %> + <% #else %> + + <% #end %> </tbody> </table> <br> +<h3>Alerts</h3> +<% conversations = current_user.mailbox.conversations %> -<%= link_to 'New Pm', new_pm_path %> +<table> + <col width="150"> + <col width="650"> + <tbody> + <%# if conversations.reject { |c| c.is_read?(current_user) || (c.receipts_for current_user).last.message.sender == current_user }.empty? %> + <tr> + <tr> + <td><b>With</b></td> + <td><b>Body</b></td> + </tr> + <% conversations.each do |conversation| %> + <% receipts = conversation.receipts_for current_user %> + <% if conversation.is_read?(current_user) || receipts.last.message.sender == current_user %> + <% message = receipts.last.message %> + <tr> + <% if conversation.subject == "Pay Attention!" && message.sender != current_user %> + <td> + <% people = conversation.participants %> + <% people.each do |person| %> + <% unless person == current_user %> + <%= truncate(person.user_name, length: 20) %> + <% end %> + <% end %> + </td> + <td><%= truncate(message.body, length: 80) %></td> + <td><%= link_to 'View', @pms.find_by(conversation: conversation) %></td> + <% end %> + </tr> + <% end %> + <% end %> + </tr> + <% #else %> + + <% #end %> + </tbody> +</table> + +<br> +<br> +<br> diff --git a/app/views/pms/show.html.erb b/app/views/pms/show.html.erb index 2f3b944..5ef0b41 100644 --- a/app/views/pms/show.html.erb +++ b/app/views/pms/show.html.erb @@ -1,29 +1,69 @@ -<p id="notice"><%= notice %></p> +<% +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +%> +<%= link_to '« Back to all private messages', pms_path, class: :breadcrumb %> -<p> - <strong>Author:</strong> - <%= @pm.author %> -</p> +<div> + <p> + <strong>Participants:</strong> + <% receps = @pm.conversation.participants %> + <% receps.each do |recep| %> + <% #unless recep == @pm.conversation.last_sender %> + <%= recep.user_name %>, + <% #end %> + <% end %> + <%= "and the NSA" %> + </p> -<p> - <strong>Recipient:</strong> - <%= @pm.recipient %> -</p> + <p> + <strong>Subject:</strong> + <%= @pm.conversation.subject %> + </p> +</div> -<p> - <strong>Message:</strong> - <%= @pm.message %> -</p> +<% receipts = @pm.conversation.receipts_for current_user %> +<% receipts.each do |receipt| %> + <% message = receipt.message %> + <hr> + <p> + <strong><%= message.sender.user_name %>:</strong> + <%= message.body %> + </p> +<% end %> -<p> - <strong>Subject:</strong> - <%= @pm.subject %> -</p> +<hr> -<p> - <strong>Conversation:</strong> - <%= @pm.conversation %> -</p> +<div> + <% @pm.message = "" %> + <%= form_for(@pm) do |f| %> + <div class="field"> + <%= f.text_area :message %> + </div> -<%= link_to 'Edit', edit_pm_path(@pm) %> | -<%= link_to 'Back', pms_path %> + <div class="actions"> + <%= submit_tag("Reply", :class => "signup") %> + </div> + <% end %> +</div> + +<% @pm.conversation.mark_as_read(current_user) %> diff --git a/app/views/search/go.html.erb b/app/views/search/go.html.erb new file mode 100644 index 0000000..4784ba9 --- /dev/null +++ b/app/views/search/go.html.erb @@ -0,0 +1,68 @@ +<% +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +%> +<div id="advanced_search" class="collapsible"> + <div class="collapsed"> + <h5><a href="#advanced_search">Advanced Search [show]</a></h5> + </div> + <div class="expanded"> + <h5><a href="#collapse">Advanced Search [hide]</a></h5> + <%= form_tag("/search", method: "get") do %> + <div class="form-group"> + <%= label_tag :query, 'Find:' %> + <%= text_field_tag(:query, params[:query]) %> + </div> + <div class="form-group"> + <%= label_tag :game_type, 'Game Type:' %> + <%= select_tag(:game_type, options_from_collection_for_select(@games, 'id', 'name'), :prompt => 'All Games') %> + </div> + <div> + <%= submit_tag("Search", :name=>nil) %> + </div> + <% end %> + </div> +</div> + +<%# Show search results if a query was not nill %> +<% if !@query.nil? and !@query.empty? %> + + <% if @tournaments.empty? and @players.empty? %> + <h3> No results found for "<%= @query %>" </h3> + <% else %> + <h3> Showing results for: <span><%= @query %></span></h3> + <% if @tournaments.length > 0 %> + <h4> Tournaments </h4> + <% end %> + <% @tournaments.each do |t| %> + <%= render "common/show_tournament", :target => t %> + <% end %> + + <% if @players.length > 0 %> + <h4> Players </h4> + <% end %> + <% @players.each do |p| %> + <%= render "common/show_user", :target => p %> + <% end %> + + <% end %> +<% end %> diff --git a/app/views/servers/_form.html.erb b/app/views/servers/_form.html.erb index 6211f9a..0cdaf15 100644 --- a/app/views/servers/_form.html.erb +++ b/app/views/servers/_form.html.erb @@ -1,21 +1,39 @@ +<% +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +%> <%= form_for(@server) do |f| %> - <% if @server.errors.any? %> - <div id="error_explanation"> - <h2><%= pluralize(@server.errors.count, "error") %> prohibited this server from being saved:</h2> + <%= render "common/error_messages", :target => @server %> - <ul> - <% @server.errors.full_messages.each do |msg| %> - <li><%= msg %></li> - <% end %> - </ul> - </div> - <% end %> + <%= fields_for "server[default_user_abilities]", @server.default_user_abilities do |a| %> + <fieldset> + <legend>Default permissions for new users</legend> + <ul> + <% @server.default_user_abilities.keys.each do |ability| %> + <li><%= a.check_box(ability) %> <%= a.label(ability) %></li> + <% end %> + </ul> + </fieldset> + <% end %> - <div class="field"> - <%= f.label :default_user_permissions %><br> - <%= f.number_field :default_user_permissions %> - </div> - <div class="actions"> - <%= f.submit %> - </div> + <%= f.submit %> <% end %> diff --git a/app/views/servers/edit.html.erb b/app/views/servers/edit.html.erb index a92cdb5..f29a65c 100644 --- a/app/views/servers/edit.html.erb +++ b/app/views/servers/edit.html.erb @@ -1,6 +1,3 @@ <h1>Editing server</h1> <%= render 'form' %> - -<%= link_to 'Show', @server %> | -<%= link_to 'Back', servers_path %> diff --git a/app/views/servers/index.html.erb b/app/views/servers/index.html.erb deleted file mode 100644 index b3064f4..0000000 --- a/app/views/servers/index.html.erb +++ /dev/null @@ -1,27 +0,0 @@ -<h1>Listing servers</h1> - -<table> - <thead> - <tr> - <th>Default user permissions</th> - <th></th> - <th></th> - <th></th> - </tr> - </thead> - - <tbody> - <% @servers.each do |server| %> - <tr> - <td><%= server.default_user_permissions %></td> - <td><%= link_to 'Show', server %></td> - <td><%= link_to 'Edit', edit_server_path(server) %></td> - <td><%= link_to 'Destroy', server, method: :delete, data: { confirm: 'Are you sure?' } %></td> - </tr> - <% end %> - </tbody> -</table> - -<br> - -<%= link_to 'New Server', new_server_path %> diff --git a/app/views/servers/index.json.jbuilder b/app/views/servers/index.json.jbuilder deleted file mode 100644 index 3c9df60..0000000 --- a/app/views/servers/index.json.jbuilder +++ /dev/null @@ -1,4 +0,0 @@ -json.array!(@servers) do |server| - json.extract! server, :id, :default_user_permissions - json.url server_url(server, format: :json) -end diff --git a/app/views/servers/new.html.erb b/app/views/servers/new.html.erb deleted file mode 100644 index 0422009..0000000 --- a/app/views/servers/new.html.erb +++ /dev/null @@ -1,5 +0,0 @@ -<h1>New server</h1> - -<%= render 'form' %> - -<%= link_to 'Back', servers_path %> diff --git a/app/views/servers/show.html.erb b/app/views/servers/show.html.erb index b18f09f..54aaf66 100644 --- a/app/views/servers/show.html.erb +++ b/app/views/servers/show.html.erb @@ -1,9 +1,6 @@ -<p id="notice"><%= notice %></p> - <p> <strong>Default user permissions:</strong> <%= @server.default_user_permissions %> </p> -<%= link_to 'Edit', edit_server_path(@server) %> | -<%= link_to 'Back', servers_path %> +<%= link_to 'Edit', edit_server_path %> diff --git a/app/views/sessions/_form.html.erb b/app/views/sessions/_form.html.erb deleted file mode 100644 index 90ad0ad..0000000 --- a/app/views/sessions/_form.html.erb +++ /dev/null @@ -1,25 +0,0 @@ -<%= form_for(@session) do |f| %> - <% if @session.errors.any? %> - <div id="error_explanation"> - <h2><%= pluralize(@session.errors.count, "error") %> prohibited this session from being saved:</h2> - - <ul> - <% @session.errors.full_messages.each do |msg| %> - <li><%= msg %></li> - <% end %> - </ul> - </div> - <% end %> - - <div class="field"> - <%= f.label :user_id %><br> - <%= f.text_field :user_id %> - </div> - <div class="field"> - <%= f.label :token %><br> - <%= f.text_field :token %> - </div> - <div class="actions"> - <%= f.submit %> - </div> -<% end %> diff --git a/app/views/sessions/edit.html.erb b/app/views/sessions/edit.html.erb deleted file mode 100644 index bbd8407..0000000 --- a/app/views/sessions/edit.html.erb +++ /dev/null @@ -1,6 +0,0 @@ -<h1>Editing session</h1> - -<%= render 'form' %> - -<%= link_to 'Show', @session %> | -<%= link_to 'Back', sessions_path %> diff --git a/app/views/sessions/index.html.erb b/app/views/sessions/index.html.erb deleted file mode 100644 index 43a7e1f..0000000 --- a/app/views/sessions/index.html.erb +++ /dev/null @@ -1,29 +0,0 @@ -<h1>Listing sessions</h1> - -<table> - <thead> - <tr> - <th>User</th> - <th>Token</th> - <th></th> - <th></th> - <th></th> - </tr> - </thead> - - <tbody> - <% @sessions.each do |session| %> - <tr> - <td><%= session.user %></td> - <td><%= session.token %></td> - <td><%= link_to 'Show', session %></td> - <td><%= link_to 'Edit', edit_session_path(session) %></td> - <td><%= link_to 'Destroy', session, method: :delete, data: { confirm: 'Are you sure?' } %></td> - </tr> - <% end %> - </tbody> -</table> - -<br> - -<%= link_to 'New Session', new_session_path %> diff --git a/app/views/sessions/index.json.jbuilder b/app/views/sessions/index.json.jbuilder deleted file mode 100644 index 5205ede..0000000 --- a/app/views/sessions/index.json.jbuilder +++ /dev/null @@ -1,4 +0,0 @@ -json.array!(@sessions) do |session| - json.extract! session, :id, :user_id, :token - json.url session_url(session, format: :json) -end diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb index 55c9eca..ba125e5 100644 --- a/app/views/sessions/new.html.erb +++ b/app/views/sessions/new.html.erb @@ -1,5 +1,49 @@ -<h1>New session</h1> +<% +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +%> +<h1>Sign in</h1> -<%= render 'form' %> +<div> + <%= form_tag(sessions_path, method: :post, id: :new_session) do %> + <% if params[:action] == "create" %> + <div class="alert-danger"> + <p>The username/password pair you entered did + not match our records. Check your typing and + try again.</p> + </div> + <% end %> + <div class="field"> + <%= label_tag(:username_or_email, "Username/Email") %><br/> + <%= text_field_tag(:username_or_email) %> + </div> + <div class="field"> + <%= label_tag(:password) %><br/> + <%= password_field_tag(:password) %> + </div> + <div class="field"> + <%= submit_tag("Log in", class: :signin) %> + </div> + <% end %> -<%= link_to 'Back', sessions_path %> + <p>New user? <%= link_to("Sign up now!", new_user_path) %></p> +</div> diff --git a/app/views/sessions/show.html.erb b/app/views/sessions/show.html.erb deleted file mode 100644 index 230e6bd..0000000 --- a/app/views/sessions/show.html.erb +++ /dev/null @@ -1,14 +0,0 @@ -<p id="notice"><%= notice %></p> - -<p> - <strong>User:</strong> - <%= @session.user %> -</p> - -<p> - <strong>Token:</strong> - <%= @session.token %> -</p> - -<%= link_to 'Edit', edit_session_path(@session) %> | -<%= link_to 'Back', sessions_path %> diff --git a/app/views/sessions/show.json.jbuilder b/app/views/sessions/show.json.jbuilder deleted file mode 100644 index c9efd3b..0000000 --- a/app/views/sessions/show.json.jbuilder +++ /dev/null @@ -1 +0,0 @@ -json.extract! @session, :id, :user_id, :token, :created_at, :updated_at diff --git a/app/views/simple_captcha/_simple_captcha.erb b/app/views/simple_captcha/_simple_captcha.erb index 9968910..5e9da76 100644 --- a/app/views/simple_captcha/_simple_captcha.erb +++ b/app/views/simple_captcha/_simple_captcha.erb @@ -1,37 +1,6 @@ -<style type="text/CSS"> - .simple_captcha{border: 1px solid #ccc; padding: 5px !important;} - .simple_captcha, - .simple_captcha div{display: table;} - .simple_captcha .simple_captcha_field, - .simple_captcha .simple_captcha_image{ - border: 1px solid #ccc; - margin: 0px 0px 2px 0px !important; - padding: 0px !important; - } - .simple_captcha .simple_captcha_image img{ - margin: 0px !important; - padding: 0px !important; - width: 110px !important; - } - .simple_captcha .simple_captcha_label{font-size: 12px;} - .simple_captcha .simple_captcha_field input{ - width: 150px !important; - font-size: 16px; - border: none; - background-color: #efefef; - } -</style> - -<div class='simple_captcha'> - <div class='simple_captcha_image'> - <%= simple_captcha_options[:image] %> - </div> - - <div class='simple_captcha_field'> - <%= simple_captcha_options[:field] %> - </div> - - <div class='simple_captcha_label'> - <%= simple_captcha_options[:label] %> - </div> +<div class="field simple_captcha"> + <label for="captcha"><%= simple_captcha_options[:label] %></label> + <%= simple_captcha_options[:image] %> + <span class="help-block">To make sure you aren't a robot.</span> + <%= simple_captcha_options[:field] %> </div> diff --git a/app/views/teams/index.html.erb b/app/views/teams/index.html.erb index b077e10..397bfc6 100644 --- a/app/views/teams/index.html.erb +++ b/app/views/teams/index.html.erb @@ -14,7 +14,7 @@ <tr> <td><%= link_to 'Show', team %></td> <td><%= link_to 'Edit', edit_team_path(team) %></td> - <td><%= link_to 'Destroy', team, method: :delete, data: { confirm: 'Are you sure?' } %></td> + <td><%= button_to 'Destroy', team, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> <% end %> </tbody> diff --git a/app/views/teams/show.html.erb b/app/views/teams/show.html.erb index 5b18d33..ab49d65 100644 --- a/app/views/teams/show.html.erb +++ b/app/views/teams/show.html.erb @@ -1,4 +1,2 @@ -<p id="notice"><%= notice %></p> - <%= link_to 'Edit', edit_team_path(@team) %> | <%= link_to 'Back', teams_path %> diff --git a/app/views/tournaments/_form.html.erb b/app/views/tournaments/_form.html.erb index 340efab..c478ce6 100644 --- a/app/views/tournaments/_form.html.erb +++ b/app/views/tournaments/_form.html.erb @@ -1,49 +1,140 @@ -<%= form_for(@tournament) do |f| %> - <% if @tournament.errors.any? %> - <div id="error_explanation"> - <h2><%= pluralize(@tournament.errors.count, "error") %> prohibited this tournament from being saved:</h2> - - <ul> - <% @tournament.errors.full_messages.each do |msg| %> - <li><%= msg %></li> - <% end %> - </ul> - </div> - <% end %> - - <div class="field"> - <%= f.label :game_id %><br> - <%= f.text_field :game_id %> - </div> - <div class="field"> - <%= f.label :status %><br> - <%= f.number_field :status %> - </div> - <div class="field"> - <%= f.label :name %><br> - <%= f.text_field :name %> - </div> - <div class="field"> - <%= f.label :min_players_per_team %><br> - <%= f.number_field :min_players_per_team %> - </div> - <div class="field"> - <%= f.label :max_players_per_team %><br> - <%= f.number_field :max_players_per_team %> - </div> - <div class="field"> - <%= f.label :min_teams_per_match %><br> - <%= f.number_field :min_teams_per_match %> - </div> - <div class="field"> - <%= f.label :max_teams_per_match %><br> - <%= f.number_field :max_teams_per_match %> - </div> - <div class="field"> - <%= f.label :scoring_method %><br> - <%= f.text_field :scoring_method %> - </div> - <div class="actions"> - <%= f.submit %> - </div> +<% +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +%> +<%= render "common/error_messages", :target => @tournament %> +<fieldset> + <legend>Basic information</legend> + <%= form_for(@tournament, url: new_tournament_path, method: "get") do |f| %> + <div class="field"> + <%= f.label :game_id, "Select a Game Type" %> + <%= f.select(:game_id, Game.all.map{|game| [game.name, game.id]}) %> + </div> + <div class="field"> + <label for="num_stages">Number of Tournament Stages</label> + <input type="number" id="num_stages" name="num_stages" min="1" value="<%= params[:num_stages].to_i %>"> + </div> + <% if @tournament.game %> + <%= f.submit("Update (Will reset the rest of the form)", class: "btn-danger") %> + <% else %> + <%= f.submit("Submit") %> + <% end %> + <% end %> + +</fieldset> +<% if @tournament.game %> + <%= form_for(@tournament, url: tournaments_path, method: "post") do |f| %> + <fieldset> + <legend>Attributes</legend> + + <%= f.hidden_field(:game_id) %> + <div class="field"> + <%= f.label :name %> + <%= f.text_field :name %> + </div> + + <table> + <tbody> + <tr> + <td></td> + <th>Minimum</th> + <th>Maximum</th> + </tr> + <tr> + <th>Players per Team: </th> + <td><%= f.number_field(:min_players_per_team, min: 1) %></td> + <td><%= f.number_field(:max_players_per_team, min: 1) %></td> + </tr> + <tr> + <th>Teams per Match: </th> + <td><%= f.number_field(:min_teams_per_match, min: 1) %></td> + <td><%= f.number_field(:max_teams_per_match, min: 1) %></td> + </tr> + </tbody> + </table> + + <div class="field"> + <%= f.label :scoring_method, :scoring_method.to_s.titleize %> + <%= f.select(:scoring_method, @tournament.scoring_methods.map{|method| [method.humanize.titleize, method]}) %> + </div> + </fieldset> + + <fieldset> + <legend>Settings</legend> + <%= f.fields_for :settings do |setting_fields| %> + <% @tournament.tournament_settings.each do |setting| %> + <div class="field"> + <%= setting_fields.label setting.name, setting.name.to_s.titleize %> + <br> + <% case setting.vartype %> + <% when 0 %> + <%= setting_fields.text_field( setting.name ) %> + <% when 1 %> + <%= setting_fields.text_area( setting.name ) %> + <% when 2 %> + <ul> + <% setting.type_opt.split(',').each do |option|%> + <li><label><%= setting_fields.radio_button( setting.name, option ) %><%= option.humanize.titleize %></label></li> + <% end %> + </ul> + <% when 3 %> + <ul> + <% setting.type_opt.split(',').each do |option|%> + <li><label><%= check_box_tag("tournament[settings][#{setting.name}][]", option, setting.value.split(',').include?(option)) %><%= option.humanize.titleize %></label></li> + <% end %> + </ul> + <% when 4 %> + <%= setting_fields.radio_button( setting.name, "true" ) %> <b>True</b> + <%= setting_fields.radio_button( setting.name, "false" ) %> <b>False</b> + <% when 5 %> + <%= setting_fields.select( setting.name, setting.type_opt.split(',').collect {|opt| opt.humanize.titleize} ) %> + <% end %> + </div> + <% end %> + <% end %> + </fieldset> + + <%= f.fields_for :stages do |stages_fields| %> + <fieldset> + <legend>Stages</legend> + <% for i in 1..(params[:num_stages].to_i) do %> + <%= stages_fields.fields_for i.to_s do |stage_fields| %> + <fieldset> + <legend>Stage <%= i %></legend> + <div class="field"> + <%= stage_fields.label :scheduling_method, :scheduling_method.to_s.titleize %> + <%= stage_fields.select(:scheduling_method, @tournament.scheduling_methods.map{|method| [method.humanize.titleize, method]}) %> + </div> + <div class="field"> + <%= stage_fields.label :seeding_method, :seeding_method.to_s.titleize %> + <%= stage_fields.select(:seeding_method, @tournament.seeding_methods.map{|method| [method.humanize.titleize, method]}) %> + </div> + </fieldset> + <% end %> + <% end %> + </fieldset> + <% end %> + + <%= f.submit %> + + <% end %> <% end %> diff --git a/app/views/tournaments/index.html.erb b/app/views/tournaments/index.html.erb index 7bf8aa0..06e1b25 100644 --- a/app/views/tournaments/index.html.erb +++ b/app/views/tournaments/index.html.erb @@ -1,41 +1,24 @@ -<h1>Listing tournaments</h1> +<h1>Listing Tournaments</h1> -<table> - <thead> - <tr> - <th>Game</th> - <th>Status</th> - <th>Name</th> - <th>Min players per team</th> - <th>Max players per team</th> - <th>Min teams per match</th> - <th>Max teams per match</th> - <th>Scoring method</th> - <th></th> - <th></th> - <th></th> - </tr> - </thead> +<div id="tournament-list"> + <% if @tournaments.length > 0 %> - <tbody> - <% @tournaments.each do |tournament| %> - <tr> - <td><%= tournament.game %></td> - <td><%= tournament.status %></td> - <td><%= tournament.name %></td> - <td><%= tournament.min_players_per_team %></td> - <td><%= tournament.max_players_per_team %></td> - <td><%= tournament.min_teams_per_match %></td> - <td><%= tournament.max_teams_per_match %></td> - <td><%= tournament.scoring_method %></td> - <td><%= link_to 'Show', tournament %></td> - <td><%= link_to 'Edit', edit_tournament_path(tournament) %></td> - <td><%= link_to 'Destroy', tournament, method: :delete, data: { confirm: 'Are you sure?' } %></td> - </tr> - <% end %> - </tbody> -</table> + <%# Each tournament has a div for its listing %> + <% @tournaments.each do |t| %> + <%= render "common/show_tournament", :target => t %> + <% end %> -<br> + <% else %> -<%= link_to 'New Tournament', new_tournament_path %> + <p class="no-entries"> No tournaments going on right now... + <% if current_user.can?(:create_tournament) %> + Why not start your own? + <% end %> + </p> + + <% end %> +</div> + +<% if current_user.can?(:create_tournament) %> + <div><%= link_to 'New Tournament', new_tournament_path, :class => "btn btn-warning btn-lg" %></div> +<% end %> diff --git a/app/views/tournaments/join.html.erb b/app/views/tournaments/join.html.erb new file mode 100644 index 0000000..1d38d68 --- /dev/null +++ b/app/views/tournaments/join.html.erb @@ -0,0 +1,2 @@ + <%= @user.name %> + diff --git a/app/views/tournaments/new.html.erb b/app/views/tournaments/new.html.erb index 2a60539..9c741e0 100644 --- a/app/views/tournaments/new.html.erb +++ b/app/views/tournaments/new.html.erb @@ -1,4 +1,4 @@ -<h1>New tournament</h1> +<h1>New Tournament</h1> <%= render 'form' %> diff --git a/app/views/tournaments/show.html.erb b/app/views/tournaments/show.html.erb index ca65ac6..55a8f14 100644 --- a/app/views/tournaments/show.html.erb +++ b/app/views/tournaments/show.html.erb @@ -1,44 +1,122 @@ -<p id="notice"><%= notice %></p> - -<p> - <strong>Game:</strong> - <%= @tournament.game %> -</p> - -<p> - <strong>Status:</strong> - <%= @tournament.status %> -</p> - -<p> - <strong>Name:</strong> - <%= @tournament.name %> -</p> - -<p> - <strong>Min players per team:</strong> - <%= @tournament.min_players_per_team %> -</p> - -<p> - <strong>Max players per team:</strong> - <%= @tournament.max_players_per_team %> -</p> - -<p> - <strong>Min teams per match:</strong> - <%= @tournament.min_teams_per_match %> -</p> - -<p> - <strong>Max teams per match:</strong> - <%= @tournament.max_teams_per_match %> -</p> - -<p> - <strong>Scoring method:</strong> - <%= @tournament.scoring_method %> -</p> - -<%= link_to 'Edit', edit_tournament_path(@tournament) %> | -<%= link_to 'Back', tournaments_path %> +<% +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +%> +<%= link_to '« Back to all tournaments', tournaments_path, class: :breadcrumb %> + +<h2 id="tournament-name"> + <%= @tournament.name %> +</h2> + +<div class="progress"> + <%# FIXME: What's up with this? Hardcoded 60%? %> + <%= tag("div", {:id => "prog-bar", :class => "progress-bar progress-bar-warning", :style => "width: " +(@tournament.players.count * 100 / (@tournament.min_players_per_team * @tournament.min_teams_per_match)).to_s + "%", "aria-valuemax" => "100", "aria-valuemin" => "0", "aria-valuenow" => (@tournament.players.count * 100 / (@tournament.min_players_per_team * @tournament.min_teams_per_match)).to_s, "role" => "progressbar"}) %> + <span class="sr-only">60% Complete (warning)</span> + </div> +</div> + +<p id="players-needed"><%= pluralize(@tournament.players.count, "player has", "players have") %> signed up. <%= @tournament.min_players_per_team * @tournament.min_teams_per_match %> needed. </p> + +<div id="tournament-side-params"> + <p> + <strong>Status:</strong> + <% if @tournament.status == 0 %> + Waiting for players... + <% else %> + Started + <% end %> + </p> + + <p> + <strong>Name:</strong> + <%= @tournament.name %> + </p> + + <p> + <strong>Min players per team:</strong> + <%= @tournament.min_players_per_team %> + </p> + + <p> + <strong>Max players per team:</strong> + <%= @tournament.max_players_per_team %> + </p> + + <p> + <strong>Min teams per match:</strong> + <%= @tournament.min_teams_per_match %> + </p> + + <p> + <strong>Max teams per match:</strong> + <%= @tournament.max_teams_per_match %> + </p> + + <p> + <strong>Scoring method:</strong> + <%= @tournament.scoring_method.titleize %> + </p> + + <% @tournament.settings.each do |setting| %> + <p> + <strong><%= setting.name %></strong> + <%= setting.value %> + </p> + <% end %> +</div> + +<div> + <% if @tournament.players.length > 0 %> + <h3>Players Here:</h3> + <ul id="tournament-users"> + <% @tournament.players.each do |p| %> + <li><%= p.user_name %></li> + <% end %> + </ul> + <% else %> + <h3 div="players-needed">Hmmm.... nobody's here yet! You and your friends should join the tournament.</h3> + <% end %> +</div> + +<div class="actions"> + <%# If user can join, and user hasn't joined already, show a join tournment button %> + <% if @tournament.joinable_by?(current_user) && !@tournament.players.include?(current_user) %> + <%= form_tag(tournament_path(@tournament), method: "put", class: :button_to) do %> + <input type="hidden" name="update_action" value="join"> + <%= submit_tag("Join Tournament") %> + <% end %> + <% elsif @tournament.players.include?(current_user) %> + <%= form_tag(tournament_path(@tournament), method: "put", class: :button_to) do %> + <input type="hidden" name="update_action" value="leave"> + <%= submit_tag("Leave Tournament") %> + <% end %> + <% end %> + <%# If user is the host, let them start the tournment %> + <% if @tournament.check_permission(current_user, :edit) %> + <%= form_tag(tournament_path(@tournament), method: "put", class: :button_to) do %> + <input type="hidden" name="update_action" value="start"> + <%= submit_tag("Start Tournament", disabled: (@tournament.players.count < @tournament.min_players_per_team * @tournament.min_teams_per_match)) %> + <% end %> + <%= link_to 'Edit Tournament', edit_tournament_path(@tournament), role: :button %> + <%= button_to 'Cancel Tournament', @tournament, method: :delete, data: { confirm: 'Are you sure?' } %> + <% end %> +</div> diff --git a/app/views/tournaments/standings.html.erb b/app/views/tournaments/standings.html.erb new file mode 100644 index 0000000..15dd325 --- /dev/null +++ b/app/views/tournaments/standings.html.erb @@ -0,0 +1,51 @@ +<% +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +%> +<% playerscores = @tournament.players.collect {|player| player => @tournament.statistics.where(match: player.matches.last, user: player, name: :score) } %> +<% teams = tournament_stage.matches.collect +{ |match| match.teams.collect { |team| team.id => team.players.collect +{ |player| player.user_name => @tournament.statistics.where(match: player.matches.last, user: player, name: :score } } } %> + +<table> + <tr> + <td>Standings:</td> + <% place = 0 %> + <% playerscores.sort {|player1, player2| playerscores[player1] <=> playerscores[player2] }.each |player| %> + <td><%= place.to_s + ":" %> <%= player.user_name %></td> + <% place += 1%> + <% end %> + </tr> +</table> + +<% teams.each do |team| %> + <table> + <tr> + <td>Standings:</td> + <% place = 0 %> + <% team.values.sort {|player1, player2| playerscores[player1] <=> playerscores[player2] }.each |player| %> + <td><%= place.to_s + ":" %> <%= player.user_name %></td> + <% place += 1%> + <% end %> + </tr> + </table> +<% end %> diff --git a/app/views/users/_form.html.erb b/app/views/users/_form.html.erb index 4d28738..e4e49f4 100644 --- a/app/views/users/_form.html.erb +++ b/app/views/users/_form.html.erb @@ -1,29 +1,68 @@ +<% +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +%> <%= form_for(@user) do |f| %> - <% if @user.errors.any? %> - <div id="error_explanation"> - <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2> - - <ul> - <% @user.errors.full_messages.each do |msg| %> - <li><%= msg %></li> - <% end %> - </ul> - </div> - <% end %> + <%= render "common/error_messages", :target => @user %> <div class="field"> <%= f.label :name %><br> <%= f.text_field :name %> </div> + <div class="field"> <%= f.label :email %><br> - <%= f.text_field :email %> + <%= f.email_field(:email) %> </div> + <div class="field"> <%= f.label :user_name %><br> <%= f.text_field :user_name %> </div> + + <div> + <%= f.label(:password, "New Password (or use old)") %><br> + <%= f.password_field :password %> + </div> + <div> + <%= f.label(:password_confirmation, "Confirm Password") %><br> + <%= f.password_field :password_confirmation %> + </div> + + <% if current_user.can? :edit_permissions %> + <fieldset> + <legend>User permissions</legend> + <ul> + <%= fields_for "user[abilities]", @user.abilities do |abilities_fields| %> + <% @user.abilities.keys.each do |ability| %> + <li><%= abilities_fields.check_box(ability) %> <%= abilities_fields.label(ability) %></li> + <% end %> + <% end %> + </ul> + </fieldset> + <% end %> + <div class="actions"> <%= f.submit %> </div> + <% end %> diff --git a/app/views/users/already_signed_in.html.erb b/app/views/users/already_signed_in.html.erb new file mode 100644 index 0000000..04b4248 --- /dev/null +++ b/app/views/users/already_signed_in.html.erb @@ -0,0 +1 @@ +<h1>You are currently signed in</h1> diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb index 99bd4cc..52f32a2 100644 --- a/app/views/users/edit.html.erb +++ b/app/views/users/edit.html.erb @@ -3,4 +3,4 @@ <%= render 'form' %> <%= link_to 'Show', @user %> | -<%= link_to 'Back', users_path %> +<%= link_to 'Users', users_path %> diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index 3692112..53b37b8 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -1,8 +1,32 @@ +<% +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +%> <h1>Listing users</h1> -<table> +<table class="table table-hover"> <thead> <tr> + <th>Username</th> <th>Name</th> <th>Email</th> <th>User name</th> @@ -15,12 +39,13 @@ <tbody> <% @users.each do |user| %> <tr> + <td><%= image_tag('http://www.gravatar.com/avatar/' + Digest::MD5.hexdigest(user.email) + '?s=30&d=identicon') %> <%= link_to("#{user.user_name}", user, nil) %></td> <td><%= user.name %></td> - <td><%= user.email %></td> + <td> ******* </td> <td><%= user.user_name %></td> <td><%= link_to 'Show', user %></td> <td><%= link_to 'Edit', edit_user_path(user) %></td> - <td><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %></td> + <td><%= button_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> <% end %> </tbody> diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb index efc0404..1445a0d 100644 --- a/app/views/users/new.html.erb +++ b/app/views/users/new.html.erb @@ -1,5 +1,70 @@ -<h1>New user</h1> +<% +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +%> +<h1> Sign Up </h1> -<%= render 'form' %> +<%= form_for @user do |f| %> + <%= render "common/error_messages", :target => @user %> + <div class="field"> + <%= f.label :user_name %> + <span class="help-block"> + This is the name you will use to log in. + It may consist of letters, numbers, <kbd>_</kbd>, and <kbd>-</kbd>. + </span> + <br> + <%= f.text_field :user_name %> + </div> + <div class="field"> + <%= f.label :password %> + <span class="help-block"> + Must be at least 6 characters long. + </span> + <br> + <%= f.password_field(:password, required: :required, minlength: 6) %> + </div> + <div class="field"> + <%= f.label(:password_confirmation, "Confirm Password") %><br> + <%= f.password_field(:password_confirmation, required: :required, minlength: 6) %> + </div> + <div class="field"> + <%= f.label :name %> + <span class="help-block"> + A display name; perhaps your real name, or perhaps an alias. + </span> + <br> + <%= f.text_field :name %> + </div> + <div class="field"> + <%= f.label :email %><br> + <%= f.email_field(:email) %> + </div> + <div class="field"> + <%= show_simple_captcha %> + </div> + <div class="actions"> + <%= f.submit("Be a Leaguer", :class => "signup") %> + </div> +<% end %> + +<%= link_to 'Already Have an Account? Log in', new_session_path, :class => "signin" %> -<%= link_to 'Back', users_path %> diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 9455a3c..ab17b9a 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -1,19 +1,79 @@ -<p id="notice"><%= notice %></p> +<% +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. +%> + +<h1> <%= @user.user_name %>'s Profile </h1> <p> - <strong>Name:</strong> - <%= @user.name %> + <%= image_tag 'http://www.gravatar.com/avatar/' + Digest::MD5.hexdigest(@user.email) + '?s=100&d=identicon' %> </p> <p> - <strong>Email:</strong> - <%= @user.email %> -</p> + <strong>Name:</strong> + <%= @user.name %> +<p> <p> - <strong>User name:</strong> - <%= @user.user_name %> + <strong>Alias:</strong> + <%= @user.user_name %> +<p> + <strong>Email:</strong> + <%= @user.email %> </p> -<%= link_to 'Edit', edit_user_path(@user) %> | -<%= link_to 'Back', users_path %> +<% if @user.remote_usernames.empty? and @user.check_permission(current_user, :edit) %> + <%= form_for @user do |f| %> + <div class="field"> + <label for="summoner_name">Have a League of Legends Account?</label> + <div class="simple-input-group"> + <input type="text" id="summoner_name" name="user[remote_usernames][League of Legends]"> + <%= f.submit "Add Username", :class => 'signup' %> + </div> + </div> + <% end %> +<% end %> + +<div class="row"> + <div class="col-md-6"> + <h3>Recent Tournaments Played</h3> + <ul> + <% @user.tournaments_played.each do |t| %> + <li><%= t.name %></li> + <% end %> + </ul> + </div> + <div class="col-md-6"> + <h3>Recent Tournaments Hosted</h3> + <% if @user.tournaments_hosted.empty? %> + <p><%= @user.user_name %> has never hosted a tournament.</p> + <% else %> + <ul> + <% @user.tournaments_hosted.each do |t| %> + <li><%= t.name %></li> + <% end %> + </ul> + <% end %> + </div> +</div> + +<%= link_to 'Edit', edit_user_path(@user), role: :button %> diff --git a/bin/autoindent b/bin/autoindent index 52c61c6..0023431 100755 --- a/bin/autoindent +++ b/bin/autoindent @@ -1,4 +1,22 @@ #!/usr/bin/env bash + +# Copyright (C) 2014 Luke Shumaker +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + file=$1 sed -i 's/^\s*//' "$file" emacs --batch "$file" \ diff --git a/bin/devel/db-drop b/bin/devel/db-drop new file mode 100755 index 0000000..06e24dc --- /dev/null +++ b/bin/devel/db-drop @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +bindir="$(dirname "$(dirname "$(readlink -f "$0")")")" +PATH=$bindir:$PATH + +set -e +rake db:drop +rake db:setup diff --git a/bin/devel/generate b/bin/devel/generate index d71b454..077f18e 100755 --- a/bin/devel/generate +++ b/bin/devel/generate @@ -1,8 +1,30 @@ #!/usr/bin/env bash +# Copyright (C) 2014 Andrew Murrell +# Copyright (C) 2014 Davis Webb +# Copyright (C) 2014 Guntas Grewal +# Copyright (C) 2014 Luke Shumaker +# Copyright (C) 2014 Nathaniel Foy +# Copyright (C) 2014 Tomer Kimia +# +# This file is part of Leaguer. +# +# Leaguer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Leaguer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the Affero GNU General Public License +# along with Leaguer. If not, see <http://www.gnu.org/licenses/>. + # The generate.sh bash file is used to generate all of the necessary # .rb files to run the website - +# # To modify it and update the app do the following: # 1. Take care of uncommitted files # 2. Run `git checkout clean2` diff --git a/bin/devel/start b/bin/devel/start new file mode 100755 index 0000000..2cb44e7 --- /dev/null +++ b/bin/devel/start @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +bindir="$(dirname "$(dirname "$(readlink -f "$0")")")" +PATH=$bindir:$PATH + +rails server --daemon "$@" +delayed_job start diff --git a/bin/devel/stop b/bin/devel/stop new file mode 100755 index 0000000..e2f7858 --- /dev/null +++ b/bin/devel/stop @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +srcdir="$(dirname "$(dirname "$(dirname "$(readlink -f "$0")")")")" +PATH=${srcdir}/bin:$PATH + +kill $(<"${srcdir}/tmp/pids/server.pid") +delayed_job stop diff --git a/config/application.example.yml b/config/application.example.yml new file mode 100644 index 0000000..a98b40e --- /dev/null +++ b/config/application.example.yml @@ -0,0 +1,3 @@ +SECRET_TOKEN: 'cc884af613d0dd093f1d6c9153abac1200c5a0db923613245b80c5c3f5e9c9f9ba51712b702f2d494a22ddea8ab40601b38a41eb39eec97b50a7a2e37748b1bc' +RIOT_API_KEY: 'ad539f86-22fd-474d-9279-79a7a296ac38' +RIOT_API_REGION: 'na' diff --git a/config/application.rb b/config/application.rb index 658e0aa..13bab0b 100644 --- a/config/application.rb +++ b/config/application.rb @@ -12,6 +12,9 @@ module Leaguer # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. + config.autoload_paths += ["#{Rails.root}/lib"] + config.watchable_dirs["#{Rails.root}/lib"] = [:rb] + I18n.enforce_available_locales = true # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. diff --git a/config/database.yml b/config/database.example.yml index 51a4dd4..51a4dd4 100644 --- a/config/database.yml +++ b/config/database.example.yml diff --git a/config/initializers/leaguer_firefox_submit_hack.rb b/config/initializers/leaguer_firefox_submit_hack.rb new file mode 100644 index 0000000..2120379 --- /dev/null +++ b/config/initializers/leaguer_firefox_submit_hack.rb @@ -0,0 +1,34 @@ +# -*- ruby-indent-level: 2; indent-tabs-mode: nil -*- +# This hacks around <input type="submit"> being sized weird by replacing it with <button type="submit"> +# This was nescessary in FF28, no longer in FF30. +# I have no idea about Chrome or Safari; I imagine browsers are converging on making it sized sanely. +module ActionView + module Helpers + module FormTagHelper + + # This is modified from actionpack-4.0.2/lib/action_view/helpers/form_tag_helper.rb#submit_tag + def submit_tag(value = "Save changes", options = {}) + options = options.stringify_keys + + if disable_with = options.delete("disable_with") + message = ":disable_with option is deprecated and will be removed from Rails 4.1. " \ + "Use 'data: { disable_with: \'Text\' }' instead." + ActiveSupport::Deprecation.warn message + + options["data-disable-with"] = disable_with + end + + if confirm = options.delete("confirm") + message = ":confirm option is deprecated and will be removed from Rails 4.1. " \ + "Use 'data: { confirm: \'Text\' }' instead'." + ActiveSupport::Deprecation.warn message + + options["data-confirm"] = confirm + end + + content_tag(:button, value, { "type" => "submit", "name" => "commit", "value" => value }.update(options)) + end + + end + end +end diff --git a/config/initializers/leaguer_html5_autovalidation.rb b/config/initializers/leaguer_html5_autovalidation.rb new file mode 100644 index 0000000..82f630e --- /dev/null +++ b/config/initializers/leaguer_html5_autovalidation.rb @@ -0,0 +1,107 @@ +module ActionView + module Helpers + module Tags + class Base + def initialize_with_html5_validators(object_name, method_name, template_object, options = {}) + initialize_without_html5_validators(object_name, method_name, template_object, options) + + if /(Area|Button|Box|Field|Select)$/ =~ self.class.name and @object.respond_to? :_validators + inject_html5_validators(@object._validators[@method_name.to_sym]) + if @method_name.to_s.end_with?("_confirmation") + orig_method_name = @method_name.to_s.sub(/_confirmation$/,'').to_sym + if @object._validators[orig_method_name].any?{|v|v.is_a?(ActiveModel::Validations::ConfirmationValidator)} + inject_html5_validators(@object._validators[orig_method_name]) + end + end + end + end + alias_method_chain :initialize, :html5_validators + + private + + def inject_html5_validators(validators = []) + validators.each do |v| + # XXX: evaluate :if/:unless? + if (v.options.keys & [:if, :unless]).empty? + case v + when ActiveModel::Validations::AbsenceValidator + # The opposite of required + # XXX: perhaps disable the input? + when ActiveModel::Validations::AcceptanceValidator + # XXX: If in a text-ish input, perhaps create a pattern from :accept? + @options[:required] = :required + when ActiveRecord::Validations::AssociatedValidator + # Can't possibly do anything here + when ActiveModel::Validations::ConfirmationValidator + # Do nothing here + when ActiveModel::Validations::ExclusionValidator + # XXX: There is no simple way to do this. + when ActiveModel::Validations::FormatValidator + # XXX: Does not support :without + if v.options[:with] and not v.options[:with].is_a?(Proc) + pattern = v.options[:with].source.sub(/^\\A/,'').sub(/\\[Zz]$/,'') + pattern = "(|#{pattern})" if (v.options[:allow_nil] or v.options[:allow_blank]) + @options[:pattern] = pattern + end + when ActiveModel::Validations::InclusionValidator + # XXX: There is no simple way to do this. + when ActiveModel::Validations::LengthValidator + @options[:minlength] = v.options[:minimum] if v.options[:minimum] + @options[:maxlength] = v.options[:maximum] if v.options[:maximum] + when ActiveModel::Validations::NumericalityValidator + # XXX: Does not support :other_than + # XXX: Does not correctly handle any of these things being a Proc + @options[:required] = :required unless v.options[:allow_nil] + @options[:step] = 1 if v.options[:only_integer] + + if v.options[:greater_than] + if @options[:step] or v.options[:even] or v.options[:odd] + @options[:min] = v.options[:greater_than] + 1 + else + # Floating point limit BS + @options[:min] = v.options[:greater_than] + end + end + if v.options[:greater_than_or_equal_to] + @options[:min] = v.options[:greater_than_or_equal_to] + end + + if v.options[:less_than] + if @options[:step] or v.options[:even] or v.options[:odd] + @options[:max] = v.options[:less_than] - 1 + else + # Floating point limit BS + @options[:max] = v.options[:less_than] + end + end + if v.options[:less_than_or_equal_to] + @options[:max] = v.options[:less_than_or_equal_to] + end + + if v.options[:equal_to] + @options[:min] = @options[:max] = v.options[:equal_to] + end + + if v.options[:even] and @options[:min] + @options[:min] = @options[:min] + @options[:min] % 2 + @options[:step] = 2 + end + if v.options[:odd] and @options[:min] + @options[:min] = @options[:min] + (@options[:min]+1) % 2 + @options[:step] = 2 + end + when ActiveModel::Validations::PresenceValidator, ActiveRecord::Validations::PresenceValidator + @options[:required] = :required + when ActiveRecord::Validations::UniquenessValidator + # Can't do this without making network calls + when ActiveModel::Validations::WithValidator + # Just here for completeness; can't possibly do anything + end # case + end # if + end # each + end # def + end # class Base + end # module Tags + end # module Helpers +end # module ActionView + diff --git a/config/initializers/mailboxer.rb b/config/initializers/mailboxer.rb index 8876f80..b529481 100644 --- a/config/initializers/mailboxer.rb +++ b/config/initializers/mailboxer.rb @@ -1,7 +1,7 @@ Mailboxer.setup do |config| #Configures if you applications uses or no the email sending for Notifications and Messages - config.uses_emails = true + config.uses_emails = false #Configures the default from for the email sent for Messages and Notifications of Mailboxer config.default_from = "no-reply@mailboxer.com" diff --git a/config/initializers/permissions_system.rb b/config/initializers/permissions_system.rb new file mode 100644 index 0000000..9d1de9f --- /dev/null +++ b/config/initializers/permissions_system.rb @@ -0,0 +1,11 @@ +module ActiveRecord + class Base + def check_permission(user, verb) + user.can?("#{verb.to_s}_#{self.class.name.underscore}".to_sym) or self.owned_by?(user) + end + + def owned_by?(user) + return false + end + end +end diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb index 604d43d..fbab4b9 100644 --- a/config/initializers/secret_token.rb +++ b/config/initializers/secret_token.rb @@ -9,4 +9,8 @@ # Make sure your secret_key_base is kept private # if you're sharing your code publicly. -Leaguer::Application.config.secret_key_base = 'cc884af613d0dd093f1d6c9153abac1200c5a0db923613245b80c5c3f5e9c9f9ba51712b702f2d494a22ddea8ab40601b38a41eb39eec97b50a7a2e37748b1bc' +Leaguer::Application.config.secret_key_base = if Rails.env.development? or Rails.env.test? + ('x' * 30) # meets minimum requirement of 30 chars long +else + ENV['SECRET_TOKEN'] +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 0653957..9b7f013 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -21,3 +21,11 @@ en: hello: "Hello world" + + simple_captcha: + placeholder: "Enter the image value" + label: "Enter the code in the box:" + + message: + default: "Secret Code did not match with the Image" + user: "The secret Image and code were different" diff --git a/config/routes.rb b/config/routes.rb index 262a156..4094479 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,24 +1,46 @@ Leaguer::Application.routes.draw do - resources :brackets - resources :sessions + resources :sessions, only: [:new, :create, :destroy] resources :users resources :games - resources :tournaments - resources :pms resources :alerts resources :teams - resources :matches + resources :tournaments do + resources :matches, only: [:index, :show, :update] + resources :brackets, only: [:create, :index, :show, :edit, :update, :destroy] + end + + resource :server, only: [:show, :edit, :update] + + root to: 'main#homepage' + + get '/search', to: 'search#go' - resources :servers +end + +Leaguer::Application.routes.named_routes.module.module_eval do + def match_path(match, options={}) + tournament_match_path(match.tournament_stage.tournament, match, options) + end + def match_url(match, options={}) + tournament_match_url(match.tournament_stage.tournament, match, options) + end + def bracket_path(bracket, options={}) + tournament_bracket_path(bracket.tournament, bracket, options) + end + def bracket_url(bracket, options={}) + tournament_bracket_url(bracket.tournament, bracket, options) + end +end +if false # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". diff --git a/db/seeds.rb b/db/seeds.rb index 4edb1e8..86bf42d 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -3,5 +3,200 @@ # # Examples: # -# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) -# Mayor.create(name: 'Emanuel', city: cities.first) +# cities = City.create!([{ name: 'Chicago' }, { name: 'Copenhagen' }]) +# Mayor.create!(name: 'Emanuel', city: cities.first) +# +p = User.permission_bits +Server.create!(default_user_permissions: p[:join_tournament] | p[:create_pm] | p[:edit_pm] | p[:create_bracket]) + +league = Game.create!(name: "League of Legends", min_players_per_team: 5, max_players_per_team: 5, min_teams_per_match: 2, max_teams_per_match: 2) +league.settings.create!(display_order: 1, name: "map" , description: "Select a map to play on.", vartype: GameSetting::types[:pick_one_dropdown], type_opt: "summoners_rift,twisted_treeline,crystal_scar,haunted_abyss", default: "summoners_rift") +league.settings.create!(display_order: 2, name: "pick_type", description: "Select a pick type." , vartype: GameSetting::types[:pick_one_dropdown], type_opt: "blind_pick,draft" , default: "draft") + +chess = Game.create!(name: "Chess", min_players_per_team: 1, max_players_per_team: 1, min_teams_per_match: 2, max_teams_per_match: 2) +chess.settings.create!(display_order: 1, name: "time_control", description: "Enter a value for Time Control (ie. 5-5, 30, 6hr, or None)", vartype: GameSetting::types[:text_short], default: "") + +hearthstone = Game.create!(name: "Hearthstone", min_players_per_team: 1, max_players_per_team: 1, min_teams_per_match: 2, max_teams_per_match: 2) +hearthstone.settings.create!(display_order: 1, name: "deck_name", description: "Enter a name for your deck, be descriptive.", vartype: GameSetting::types[:text_long], default: "") + +rockpaperscissors = Game.create!(name: "Rock, Paper, Scissors", min_players_per_team: 1, max_players_per_team: 3, min_teams_per_match: 2, max_teams_per_match: 2) +rockpaperscissors.settings.create!(display_order: 4, name: "lizard_spock_allowed", description: "Will you allow Lizard and Spock?" , vartype: GameSetting::types[:true_false] , default: false) +rockpaperscissors.settings.create!(display_order: 5, name: "favorite_object" , description: "What is your favorite object in RPS?", vartype: GameSetting::types[:pick_one_radio], type_opt: "rock,paper,scissors", default: "rock") +rockpaperscissors.settings.create!(display_order: 6, name: "check_boxes" , description: "Example boxes" , vartype: GameSetting::types[:pick_several] , type_opt: "i_do_not_know,there_is_now_spoon,wow,because_electricity,wat?", default: "wow,wat?") + +if Rails.env.development? or (ENV['FORCE_SEED'] and not ENV['FORCE_SEED'].empty?) + # User 1, the ADMIN + admin = User.create!(name: "Administrator", user_name: "admin", email: "root@localhost.lan", password: "password", permissions: 0x7FFFFFFF) + + # John Doe's for testing + User.create!(name: "John 0", password: "password", email: "john0@gmail.com", user_name: "johndoe0") + User.create!(name: "John 1", password: "password", email: "john1@gmail.com", user_name: "johndoe1") + User.create!(name: "John 2", password: "password", email: "john2@gmail.com", user_name: "johndoe2") + User.create!(name: "John 3", password: "password", email: "john3@gmail.com", user_name: "johndoe3") + User.create!(name: "John 4", password: "password", email: "john4@gmail.com", user_name: "johndoe4") + User.create!(name: "John 5", password: "password", email: "john5@gmail.com", user_name: "johndoe5") + User.create!(name: "John 6", password: "password", email: "john6@gmail.com", user_name: "johndoe6") + User.create!(name: "John 7", password: "password", email: "john7@gmail.com", user_name: "johndoe7") + User.create!(name: "John 8", password: "password", email: "john8@gmail.com", user_name: "johndoe8") + User.create!(name: "John 9", password: "password", email: "john9@gmail.com", user_name: "johndoe9") + + # Users for mocked Riot API calls + players_for_league = [] + players_for_league.push(User.create!(name: "Sytrie" , password: "password", email: "Sytrie@gmail.com" , user_name: "Sytrie" )) + players_for_league.push(User.create!(name: "Derpanator115" , password: "password", email: "Derpanator115@gmail.com" , user_name: "Derpanator115" )) + players_for_league.push(User.create!(name: "Wlknexe56" , password: "password", email: "Wlknexe56@gmail.com" , user_name: "Wlknexe56" )) + players_for_league.push(User.create!(name: "DVisionzz" , password: "password", email: "DVisionzz@gmail.com" , user_name: "DVisionzz" )) + players_for_league.push(User.create!(name: "HYP3RTONIC" , password: "password", email: "HYP3RTONIC@gmail.com" , user_name: "HYP3RTONIC" )) + players_for_league.push(User.create!(name: "M9Fumjaa" , password: "password", email: "M9Fumjaa@gmail.com" , user_name: "M9Fumjaa" )) + players_for_league.push(User.create!(name: "spikevsnaruto" , password: "password", email: "spikevsnaruto@gmail.com" , user_name: "spikevsnaruto" )) + players_for_league.push(User.create!(name: "GoogleMaSkills" , password: "password", email: "GoogleMaSkills@gmail.com" , user_name: "GoogleMaSkills" )) + players_for_league.push(User.create!(name: "james chamberlan", password: "password", email: "jameschamberlan@gmail.com", user_name: "jameschamberlan" )) + players_for_league.push(User.create!(name: "Kaceytron" , password: "password", email: "Kaceytron@gmail.com" , user_name: "Kaceytron" )) + + # Semi-real users + guntas = User.create(name: "Guntas Grewal" , password: "password", email: "guntasgrewal@gmail.com" , user_name: "guntasgrewal") + luke = User.create(name: "Luke Shumaker" , password: "password", email: "lukeshu@emacs4lyfe.com" , user_name: "lukeshu" ) + tomer = User.create(name: "Tomer Kimia" , password: "password", email: "tomer@2majors4lyfe.com" , user_name: "tkimia" ) + josh = User.create(name: "Josh Huser" , password: "password", email: "jhuser@iownabusiness.net" , user_name: "WinterWorks" ) + dunsmore = User.create(name: "Professor Dunsmore", password: "password", email: "bxd@purdue.edu" , user_name: "Dumbledore" ) + marco = User.create(name: "Marco Polo" , password: "password", email: "marco@ta4lyfe.com" , user_name: "iCoordinate" ) + jordan = User.create(name: "Geoffrey Webb" , password: "password", email: "imnotjoffreybarathian@gameofthrones.com", user_name: "GTBPhoenix" ) + obama = User.create(name: "Obama" , password: "password", email: "obama@whitehouse.gov" , user_name: "Obama" ) + + davis = User.create(name: "Davis Webb" , password: "password", email: "davislwebb@gmail.com" , user_name: "TeslasMind" ) + foy = User.create(name: "Nathaniel Foy" , password: "password", email: "nfoy@purdue.edu" , user_name: "NalfeinX" ) + andrew = User.create(name: "Andrew Murrell" , password: "password", email: "murrel@murrel.gov" , user_name: "ImFromNasa" ) + joey = User.create(name: "Joseph Adams" , password: "password", email: "alpha142@fluttershyop.com" , user_name: "alpha142" ) + panda = User.create(name: "Panda" , password: "password", email: "panda@gmail.com" , user_name: "InspectorPanderp" ) + mesa = User.create(name: "Mesataki" , password: "password", email: "mesataki@gmail.com" , user_name: "Mesakati" ) + guntas_league = User.create(name: "TolkiensButt" , password: "password", email: "TolkiensButt@gmail.com" , user_name: "TolkiensButt" ) + lyra = User.create(name: "Lyra Heartstings" , password: "password", email: "LyraHeartstings@gmail.com" , user_name: "LyraHeartstings" ) + josh_league = User.create(name: "Josh_league" , password: "password", email: "josh_league@gmail.com" , user_name: "Joshoowah" ) + jeff = User.create(name: "Jeff Linguinee" , password: "password", email: "jefflingueeneeeee@gmail.com" , user_name: "SenorJeffafa" ) + sarah = User.create(name: "Sarah Lawson" , password: "password", email: "sarah@gmail.com" , user_name: "LittlexSurah" ) + + # League of Legends tournament + league_tourn = Tournament.create!( + game: league, + name: "League of Legends Seed", + min_players_per_team: 5, + max_players_per_team: 5, + min_teams_per_match: 2, + max_teams_per_match: 2, + scoring_method: "winner_takes_all", + hosts: [admin]) + league_tourn.stages.create!(scheduling_method: "round_robin" , seeding_method: "random_seeding") + players_for_league.each do |player| + league_tourn.join(player) + end + + # Chess + chess_tourn = Tournament.create!( + game: chess, + name: "Chess Seed", + min_players_per_team: 1, + max_players_per_team: 1, + min_teams_per_match: 2, + max_teams_per_match: 2, + scoring_method: "winner_takes_all", + hosts: [davis]) + chess_tourn.stages.create!(scheduling_method: "round_robin" , seeding_method: "random_seeding") + chess_tourn.join(davis) + chess_tourn.join(foy) + + # Rock Paper Scissors + rps = Tournament.create!( + game: rockpaperscissors, + name: "Rock, Paper, Scissors Seed", + min_players_per_team: 1, + max_players_per_team: 3, + min_teams_per_match: 2, + max_teams_per_match: 2, + scoring_method: "winner_takes_all", + hosts: [davis]) + rps.stages.create!(scheduling_method: "round_robin" , seeding_method: "random_seeding") + rps.join(davis) + rps.join(foy) + rps.join(guntas) + + # Another League tournament + tourn5 = Tournament.create!( + game: league, + name: "5 Teams, 2 Teams Per Match", + min_players_per_team: 1, + max_players_per_team: 1, + min_teams_per_match: 2, + max_teams_per_match: 2, + scoring_method: "winner_takes_all", + hosts: [admin]) + tourn5.stages.create!(scheduling_method: "elimination" , seeding_method: "random_seeding") + players_for_league.each do |player| + tourn5.join(player) + end + + # Yet another League tournament + tourn6 = Tournament.create!( + game_id: 1, + name: "3 teams per match", + min_players_per_team: 1, + max_players_per_team: 1, + min_teams_per_match: 3, + max_teams_per_match: 3, + scoring_method: "winner_takes_all", + hosts: [admin]) + tourn6.stages.create!(scheduling_method: "round_robin" , seeding_method: "random_seeding") + players_for_league.each do |player| + tourn6.join(player) + end + tourn6.join(davis) + tourn6.join(foy) + tourn6.join(guntas) + tourn6.join(luke) + tourn6.join(marco) + tourn6.join(jordan) + tourn6.join(obama) + tourn6.join(joey) + + #Hearthstone tournament + hearth = Tournament.create!( + game: hearthstone, + name: "Hearthstone Seed", + min_teams_per_match: 1, + min_players_per_team: 1, + max_teams_per_match: 2, + max_players_per_team: 1, + scoring_method: "winner_takes_all", + hosts: [admin]) + hearth.stages.create!(scheduling_method: "round_robin" , seeding_method: "random_seeding") + hearth.join(davis) + hearth.join(foy) + + #THE REAL GAME WE ARE PLAYING AT 10 + davis.remote_usernames.create( game: league, value: {"name" => "TeslasMind" , "id" => 30533514} ) + foy.remote_usernames.create( game: league, value: {"name" => "NalfeinX" , "id" => 29538130} ) + andrew.remote_usernames.create( game: league, value: {"name" => "ImFromNasa" , "id" => 29782091} ) + joey.remote_usernames.create( game: league, value: {"name" => "Alpha142" , "id" => 29732514} ) + sarah.remote_usernames.create( game: league, value: {"name" => "LittlexSurah" , "id" => 30613787} ) + mesa.remote_usernames.create( game: league, value: {"name" => "Mesakati" , "id" => 51552042} ) + panda.remote_usernames.create( game: league, value: {"name" => "NalfeinX" , "id" => 47953989} ) + jordan.remote_usernames.create( game: league, value: {"name" => "GTBPhoenix" , "id" => 29812020} ) + josh_league.remote_usernames.create(game: league, value: {"name" => "Joshoowah" , "id" => 26083333} ) + jeff.remote_usernames.create( game: league, value: {"name" => "SenorJeffafa" , "id" => 32612067} ) + lyra.remote_usernames.create( game: league, value: {"name" => "Lyra Heartstings", "id" => 32240762} ) + + g = [davis, joey, panda, mesa, lyra, jordan, jeff, sarah, foy, andrew] + + custom = Tournament.create!( + game: league, + name: "Real League Game", + min_players_per_team: 5, + max_players_per_team: 5, + min_teams_per_match: 2, + max_teams_per_match: 2, + scoring_method: "winner_takes_all", + hosts: [admin]) + custom.stages.create!(scheduling_method: "round_robin" , seeding_method: "early_bird_seeding") + g.each do |player| + custom.join(player) + end +end diff --git a/doc/Sprint1-Retrospective.md b/doc/Sprint1-Retrospective.md new file mode 100644 index 0000000..7bffde7 --- /dev/null +++ b/doc/Sprint1-Retrospective.md @@ -0,0 +1,220 @@ +--- +title: "Team 6 - Project Leaguer: Sprint 1 Retrospective" +author: [ Nathaniel Foy, Guntas Grewal, Tomer Kimia, Andrew Murrell, Luke Shumaker, Davis Webb ] +--- + +# User Stories + +1) As an administrator, I would like to install and boot my own server. + - Alternately: As a developer, I would like a demo/testing server, + with a basic Rails setup. +2) As a host/player, I would like to register and have an account. + - For this task, we will be creating the user registration and log + in capabilities for Leaguer. +3) As a host, I would like to start a tournament. + - For this task, we will be creating a base tournament system for a + host to run. +4) As a host/player, I would like to enter scores for players. + - For sprint own, the scores will be entered by hand. +5) As an administrator, I want to specify how users become hosts. +6) As a user I would like to see the progress of the tournament in my + browser. +7) As a user, I would like a presentable homepage. + - For this task, we will be creating a Leaguer homepage and ensure that it + is pleasing to the eye and easy to navigate. + +# Tasks + +The "size" is using the modified Fibonacci scale. A '1' is expected +to take less than an hour. A '3' is expected to take 3-6 hours. A +'5' should take the better part of a day or two. An 8 should take +several days. + ++---------------------------------------------------------+------+--------+----+ +| Tasks Implemented and Working | Size | Person | US | ++=========================================================+======+========+====+ +| [Learn Rails, set up Scaffolding for all Models, Views, | 8 | All | 1 | +| Controllers](#learn-rails) | | | | ++---------------------------------------------------------+------+--------+----+ +| [Deploy rails on Luke's server](#deploy-rails) | 3 | Luke | 1 | ++---------------------------------------------------------+------+--------+----+ +| [Create log-in system back-end (verification, cookies, | 5 | Davis | 2 | +| and redirection)](#login-backend) | | | | ++---------------------------------------------------------+------+--------+----+ +| [Create log-in system UI](#login-ui) | 2 | Tomer | 2 | ++---------------------------------------------------------+------+--------+----+ +| [Create Tournament Settings Page](#tourney-settings) | 3 | Guntas | 3 | ++---------------------------------------------------------+------+--------+----+ +| [Implement Tournament Registration and Tournament | 2 | Andrew | 3 | +| Controller](#tourney-registration) | | | | ++---------------------------------------------------------+------+--------+----+ +| [Implement match controller](#match-controller) | 3 | Dav+And| 4 | ++---------------------------------------------------------+------+--------+----+ +| [Implement permissions system over the users | 3 | Luke | 5 | +| system](#permissions) | | | | ++---------------------------------------------------------+------+--------+----+ +| [Create View Tournament Page](#tourney-view) | 5 | All | 6 | ++---------------------------------------------------------+------+--------+----+ +| [Create Presentable Homepage](#homepage) | 5 | Guntas | 7 | ++---------------------------------------------------------+------+--------+----+ + + ++---------------------------------------------------------+------+--------+----+ +| Tasks Implemented and Not Working Well | Size | Person | US | ++=========================================================+======+========+====+ +| [Design and implement match score models](#match-score) | 3 | Foy | 4 | ++---------------------------------------------------------+------+--------+----+ +| [Create Admin-level Server Management Page](#srv-man) | 2 | Luke | 5 | ++---------------------------------------------------------+------+--------+----+ + + ++---------------------------------------------------------+------+--------+----+ +| Tasks Not Implemented | Size | Person | US | ++=========================================================+======+========+====+ +| [Design/Code Scoring/Pairing Algorithms and | 5 | Foy | 3 | +| Procedures](#score-algo) | | | | ++---------------------------------------------------------+------+--------+----+ +| [Observe Foy Design/Code Scoring/Pairing | 2 | Dav+Foy| 3 | +| Algorithms](#score-algo) | | | | ++---------------------------------------------------------+------+--------+----+ +| [Create a Player-level Data Entry Page/Method for | 3 | Tomer | 4 | +| Results](#data-entry) | | | | ++---------------------------------------------------------+------+--------+----+ + +# Implemented and working + +## Learn Rails {#learn-rails} + +Learning Rails has been a growing experience for the majority of the +team. Some of us coming from no significant experience to being able +to put together a relatively functional product in only three weeks +has been an impressive journey. + +## Deploy Rails {#deploy-rails} + +The entire team became familiar with deploying Rails in our rather +diverse working environments and successfully deployed a server +instance located at demo.projectleaguer.net as well as on our local boxes. + +## Login (back-end) {#login-backend} + +Our login back-end successfully logs users in and our and can handle +user registrations and first-come-first-serve uniqueness validation. + +## Login (UI) {#login-ui} + +Our login user interface successfully differentiates between logged in +and logged out users as well as between users of different +designations (although for the demo some of the hooks were not in +place, this has been fixed). + +## Tournament settings {#tourney-settings} + +Tournament settings were implemented at a basic level, instituting those +items which are similar to all tournaments, regardless of type, orginating +from the game model. + +## Tournament registration {#tourney-registration} + +Tournament registration and the tournament contoller were completed which +allowed users to join and participate in basic tournaments of several types. +The tournament controller handled a variety of tournament related tasks, +including creating and updating tournaments and validating tournament related +operations. + +## Match controller {#match-controller} + +The Match Controller creates the separate matches for a specific tournament. +When a tournament is started, it begins with an initial match that contains +no players. Currently, a player must join a match by entering the specific +tournament (by clicking the 'show' button on the tournament), +then they must enter the match (again by clicking the 'show' button but this +time on the match they desire to participate in) and then finally clicking +the 'join' button. This updates the match with the user as a participant in +the matc and then finally clicking the 'join' button. This updates the match +with the user as a participant in the match. A match can also be destroyed +by clicking the 'delete' button on the no longer desired match on the page. + +## Permissions system {#permissions} + +The permissions system is implemented, easy to use, and works well. +In some places, it appears to be broken (overly-permisive), but this +is because the relevant page doesn't hook into the permission system. +This needs to be fixed with unit tests. + +## Tournament view {#tourney-view} +The view page for tournaments contains a table that lists all on going +tournaments for all types of games. It also lists other game attribute like +Players per team, Teams per match, whether or not teams were randomized +and also has links to Show, Edit, Destroy, Join a particular tournament. +A link to create a tournament is also provided. Each of the links correspond +to view pages which are html.erb pages that provide styles and functionality +of each of the tournament setting features. Show, Edit, Destroy, all have +views of their own to perform each of the above actions. + +## Homepage {#homepage} +The homepage is mainly controlled by the views that are associated with each +model and controller. The main view for the homepage is the one found in the +layouts called application.html.erb, this view is responsible for display of +all the headings, navigation bars, forms and containers. This view page +contains mostly links to other view pages and yields whatever the other view +pages have to offer. The Homepage redirects to Login, Signup, See Ongoing +Tournaments and shows the view for those models. + + +# Implemented but not working well + +## Match score models {#match-score} + +This only functioned properly for noting which team would win a match. We want +more information to be included, such as individual player scores. We also +only had it working where the tournament host would decide who won. + +## Server management {#srv-man} + +The server management software interface is implemented, and working +fine. The other modules use it. However, what we didn't implement is +an actual *page* to edit these settings. We had this task in the +iteration because other items depended on it. Though we did not +implement the full story, we implemented the core reason that we +wanted it. + +# Not implemented + +## Scoring Algorithms {#score-algo} + +Scoring algorithms was not implemented because we did not have time for +implementing player statistics in the first sprint. There were some +preliminary approaches, but the task lost priority and was abandoned. + +## Data entry {#data-entry} + +It was decided to not be a priority for sprint one due to time constraints. +Also, we want to implement data entry for League of Legends through +Riot Games (TM)'s API for grabbing match data. + +# How to improve + +Peer reviews and testing were our biggest pitfalls. + + +1. All testing was just manual, in-browser testing, rather than unit + tests. We really need write unit tests this iteration, as we had + breakages where we said "this is exactly why we need unit + testing." However, that happened late enough in the iteration that + we didn't have time to do anything about it. + +2. That leads us into time management. Our commit activity plotted + against time has humps each weak, each growing a little. That is, + we started slow, and ended with a lot of work. This wasn't exactly + poor planing, but we had a poor idea of how much time things would + take. We plan to fix this by front-loading this iteration instead + of back-loading it. + +3. We had the approach of "show everyone everything" with peer + reviews, as we anticipated that this would be nescessary for + everyone learning Rails. However, in effect it meant that + sometimes information was spread very thin, or because things were + being done "in the open", we didn't ever explicitly review them. + We plan on fixing this next iteration by committing to do very + specific peer reviews with just a couple members of the team. diff --git a/doc/Sprint2-Retrospective.md b/doc/Sprint2-Retrospective.md new file mode 100644 index 0000000..9a98ec5 --- /dev/null +++ b/doc/Sprint2-Retrospective.md @@ -0,0 +1,215 @@ +--- +title: "Team 6 - Project Leaguer: Sprint 2 Retrospective" +author: [ Nathaniel Foy, Guntas Grewal, Tomer Kimia, Andrew Murrell, Luke Shumaker, Davis Webb ] +--- + +# Tasks + +The "size" is using the modified Fibonacci scale. A '1' is expected +to take less than an hour. A '3' is expected to take 3-6 hours. A +'5' should take the better part of a day or two. An 8 should take +several days. + ++---------------------------------------------------------+------+------------+----+ +| Tasks Implemented and Working | Size | Person\* | US | ++=========================================================+======+============+====+ +| [Implement Anti-spam measures](#anti-spam) | 2 | Davis | 2 | ++---------------------------------------------------------+------+------------+----+ +| [Implement Teammate Rating System (peer review view)] | 5 | Guntas | 3 | +| (#peer-review) | | | | ++---------------------------------------------------------+------+------------+----+ +| [Design/Code Scoring/Pairing Algorithms and Procedures] | 5 | D+F+A | 3 | +| (#pair-alg) | | | | ++---------------------------------------------------------+------+------------+----+ +| [Implement game-type specific and tournament | 8 | L+A+G | 4 | +| specific settings and preferences] (#setting-and-pref) | | | | ++---------------------------------------------------------+------+------------+----+ +| [Retrieve data from Riot Games (TM) API ](#riot-api) | 3 | Foy | 5 | ++---------------------------------------------------------+------+------------+----+ +| [Parse Riot data and attach to scoring subsystem] | 5 | Davis | 5 | +| (#parse-riot) | | | | ++---------------------------------------------------------+------+------------+----+ +| [Teach Andrew and Tomer AJAX ](#teach-ajax) | 2 | Luke | 6 | ++---------------------------------------------------------+------+------------+----+ +| [Make pages auto-update with AJAX](#ajax) | 5 | T+A | 6 | ++---------------------------------------------------------+------+------------+----+ +| [Setting up a Tournament View for matches and tree] | 5 | Tomer | 7 | +| (#match-gui) | | | | ++---------------------------------------------------------+------+------------+----+ +| [Increase Usability](#usability) | 3 | All-L | 8 | ++---------------------------------------------------------+------+------------+----+ +| [Develop comprehensive data storage for s&p&other] | 5 | L+A | 9 | +| (#data-storage) | | | | ++---------------------------------------------------------+------+------------+----+ +| [Create Player Profile Pages](#profile) | 2 | Tomer | 10 | ++---------------------------------------------------------+------+------------+----+ +| [Gravatar Integration](#gravatar) | 2 | Foy | 10 | ++---------------------------------------------------------+------+------------+----+ + + + + ++---------------------------------------------------------+------+------------+----+ +| Tasks Implemented and Not Working Well | Size | Person\* | US | ++=========================================================+======+============+====+ +| [Not Applicable](#all-or-nothing) | 0 | --- | 0 | ++---------------------------------------------------------+------+------------+----+ + + + + ++---------------------------------------------------------+------+------------+----+ +| Tasks Not Implemented | Size | Person\* | US | ++=========================================================+======+============+====+ +| [Email Verification Option](#email-varify) | 5 | Luke | 2 | ++---------------------------------------------------------+------+------------+----+ +| [Project Leaguer Logo](#logo) | spike| D+G | 8 | ++---------------------------------------------------------+------+------------+----+ +| [Define Specific Unit Tests for Security] | 3 | All | 1 | +| (#security-test) | | | | ++---------------------------------------------------------+------+------------+----+ + + + + +# Implemented and working + +## Implement Anti-spam measures {#anti-spam} + +To handle potential spam problems, Project Leaguer has implemented Simple Captcha +on the user sign up page. Users must enter the correct code corresponding with +Simple Captcha's generated image when registering. Usernames must also be unique. +E-mail verification has been pushed to Sprint 3. + +## Implement Teammate Rating System (peer review view) {#peer-review} + +This sprint covered both the database framework and actual implementation of the +peer review rating system. Peer review was accomplished with both server-side +processing and client-side manipulation of the DOM via a floating tactile dragable +info-box interface. + +## Design/Code Scoring/Pairing Algorithms and Procedures {#pair-alg} + +Several scoring algorithms were considered for demonstration purposes for this +sprint and eventually a modified fibonachi peer review system was chosen as the +most fair system. This was the only scoring algorithm implemented in this sprint. +A single-elimination pairing algorithm was chosen for similar reasons (as well +as for simplicity in SVG generation). + +## Implement game-type specific and tournament specific settings and preferences + {#setting-and-pref} + +The input for settings and preferences for creating tournaments are displayed +dynamically in both the substance of the content and form in which it is displayed. + +## Retrieve data from Riot Games (TM) API {#riot-api} + +Grabbing League of Legends user and match data from Riot's servers has been +implemented using their newly available API. A developer key is necessary in +order to retrieve data from their servers. We currently are using Davis's to do +so. Information is grabbed with HTTParty.get and the correct url. A hash of +information is stored this way. Grabbing information for a user requires the +user's League of Legends summoner's name or summoner id. Our current developer +key is limited to utilizing 10 pulls per 10 seconds. + +## Parse Riot data and attach to scoring subsystem {#parse-riot} + +We successfully parse the data we recieve from the Riot servers. The information +is stored in a JSON hash which we separate based on the information we want (like +kills, deaths, etc). One issue with our current pull method is that it can exceed +the pull limit that is on our current development key. To fix this, we are planning +on implementing a remote user id to link users Leaguer information to their Riot +information. + +## Teach Andrew and Tomer AJAX {#teach-ajax} + +Luke instructed Tomer on his AJAX tasks, but most of Andrew's were deferred to +sprint 3 and he focused his efforts elsewhere. + +## Make pages auto-update with AJAX {#ajax} + +AJAX was used in tournament and match views to update the tournament progress bar +and manage input options for tournament flow but still needs to be implemented +across the website in other areas. + +## Setting up a Tournament View for matches and tree {#match-gui} + +A new system was set-up so that matches are created from the trunk (final match) to +the most out matches, and teams are inserted into matches starting at the leaves and +and filling up the trunk. Any number of teams is now supported. A lot of log-based +math was used to write the rails-generated SVG, and a lot of arithmetic was done to +calculate the relative proportions. + +## Increase Usability {#usability} + +Project Leaguer has many new features that have increased usability. AJAX +integration, tournament visuals (ready bar, match trees), Gravatar images, +and Riot API integration all contribute towards an easier and more automatic +web interface available for our users to utilize. + +## Develop comprehensive data storage for s&p&other{#data-storage} + +Settings and Preferences (those options specific to tournaments of a game type +or a specific tournament, respectively) are handled through a single +TournamentPreference SQL (ActiveRecord) interface. + +## Create Player Profile Pages {#profile} + +Player Profile Pages successfully list important and useful user information. +Player username, e-mail, relationship status, and recent tournament information +are all listed on a user's profile page. Gravatar images are also shown here. +Users can also edit their pages. + +## Gravatar Integration {#gravatar} + +Gravatar images are fetched from the gravatar website. A user's e-mail is used to +generate a hash key and that key is used to grab their gravatar image from a url. +If their e-mail is not recognized by Gravatar, then we have a wide number of +optionable default images to use. We currently use a mystery man default. It's +also possible to utilize a number of other image options, such as sizing. + + + +# Implemented but not working well + +## Not Applicable {#all-or-nothing} + +Everything we implemented was implemented well, or else we didn't implement it. + + + +# Not implemented + +## Email Verification Option {#email-verify} + +This was not implemented for lack of time. Luke probably would have been able to +implement it with his time constraints if he wasn't busy frequently assisting +other members with various problems. In the end, the email verification was also +simply a low priority. + +## Project Leaguer Logo {#logo} + +The Project Leaguer Logo was discussed before the sprint started. We decided we +would follow up on any opportunities to explore creating a Leauger Logo, but this +simply did not happen. We greatly want one, but it's still just a low priority +extra feature. + +## Define Specific Unit Tests for Security {#security-test} + +Because of heavy "dog-food" style testing during the development process and +fairly heavy rapid redesign, specific unit tests were not given a high priority +for this sprint. The interdependency of components for tournament logic provided +near-instant feedback when something was wrong. + +# How to improve + +1. We can better document our code with proper commentation and indentation. The +team has run into issues where we've become confused with our own code and wasted +time reviewing code. + +2. Our commits slowed to a halt the week before spring break. In this upcoming +sprint we plan to take a stronger initiative and take a running start rather than +a last lap dash. + +3. We can more carefully push and merge. We ran into a couple issues where team +members broke each others work. These mistakes cost a lot of time to fix. diff --git a/doc/Sprint2.md b/doc/Sprint2.md new file mode 100644 index 0000000..a823600 --- /dev/null +++ b/doc/Sprint2.md @@ -0,0 +1,103 @@ +--- +title: "Team 6 - Project Leaguer: Sprint 2" +author: [ Nathaniel Foy, Guntas Grewal, Tomer Kimia, Andrew Murrell, Luke Shumaker, Davis Webb ] +--- + +# User Stories + +1) As an admin, I would like hosts/players, to have only the options + their group entitles them to. + +2) As an admin, I would like anti-spam measures for registration. + +3) As a player I would like to review my peers and have our + scores reflect these reviews. + +4) As a host I would like to have both game-type specific settings and + tournament specific preferences available when creating a new + tournament. + - These settings and preferences were moved to sprint 2 from sprint 1 + because we did not have the API functionality for any specific game yet. + +5) As a host/player/spectator I would like to have Riot Games League + of Legends API integration for match and player statistics and results for + League of Legends tournaments. + +6) As a host/player, I would like my pages to actively update without + refreshing my current page. + - For this task, we will implement an Active Status Update system with AJAX. + +7) As a host/player, I would like to see an interactive tournament lobby page + that displays tournament and match information. + - This will be accomplished with dynamic graphs, trees, and status bars. + +8) As a host/player, I would like the Leaguer application to be more intuitive + and easy to use. + +9) As a user, I would like past tournament and player information to be + persistent and search-able. + - A working search utility should be implemented that will find specific + players or tournaments and return their pages. + +10) As a user, I would like to see Player Profile pages. + - For this task, we will be creating profile pages for registered users that + have player-specific information such as tournament history and activity. + +# Tasks + +The "size" is using the modified Fibonacci scale. A '1' is expected +to take less than an hour. A '3' is expected to take 3-6 hours. A +'5' should take the better part of a day or two. An 8 should take +several days. + ++---------------------------------------------------------+------+------------+----+ +| Task Description | Size | Person\* | US | ++=========================================================+======+============+====+ +| Define Specific Unit Tests for Security | 3 | All | 1 | ++---------------------------------------------------------+------+------------+----+ +| Implement Anti-spam measures | 2 | Davis | 2 | ++---------------------------------------------------------+------+------------+----+ +| Email Verification Option | 5 | Luke | 2 | ++---------------------------------------------------------+------+------------+----+ +| Implement Teammate Rating System (peer review view) | 5 | Guntas | 3 | ++---------------------------------------------------------+------+------------+----+ +| Design/Code Scoring/Pairing Algorithms and Procedures | 5 | D+F+A | 3 | ++---------------------------------------------------------+------+------------+----+ +| Implement game-type specific and tournament | 8 | L+A+G | 4 | +| specific settings and preferences | | | | ++---------------------------------------------------------+------+------------+----+ +| Retrieve data from Riot Games (TM) API | 3 | Foy | 5 | ++---------------------------------------------------------+------+------------+----+ +| Parse Riot data and attach to scoring subsystem | 5 | Davis | 5 | ++---------------------------------------------------------+------+------------+----+ +| Teach Andrew and Tomer AJAX | 2 | Luke | 6 | ++---------------------------------------------------------+------+------------+----+ +| Make pages auto-update with AJAX | 5 | T+A | 6 | ++---------------------------------------------------------+------+------------+----+ +| Setting up a Tournament View for matches and tree | 5 | Tomer | 7 | ++---------------------------------------------------------+------+------------+----+ +| Increase Usability | 3 | All-L | 8 | ++---------------------------------------------------------+------+------------+----+ +| Project Leaguer Logo | spike| D+G | 8 | ++---------------------------------------------------------+------+------------+----+ +| Develop comprehensive data storage for s&p&other | 5 | L+A | 9 | ++---------------------------------------------------------+------+------------+----+ +| Create Player Profile Pages | 2 | Tomer | 10 | ++---------------------------------------------------------+------+------------+----+ +| Gravitar Integration | 2 | Foy | 10 | ++---------------------------------------------------------+------+------------+----+ +| Test it | 1 | All-L | all| ++---------------------------------------------------------+------+------------+----+ +| Peer review | 1 | All | all| ++---------------------------------------------------------+------+------------+----+ + +Total Size of Iteration: 55 + + D = Davis = 10 + A = Andrew = 10 + F = Nathaniel = 10 + G = Guntas = 10 + L = Luke = 11 + T = Tomer = 10 + +\* `+` means those members work together, `-` means exclude following members diff --git a/doc/Sprint3-Retrospective.md b/doc/Sprint3-Retrospective.md new file mode 100644 index 0000000..831e51c --- /dev/null +++ b/doc/Sprint3-Retrospective.md @@ -0,0 +1,291 @@ +--- +title: "Team 6 - Project Leaguer: Sprint 3 Retrospective" +author: [ Nathaniel Foy, Guntas Grewal, Tomer Kimia, Andrew Murrell, Luke Shumaker, Davis Webb ] +--- + +# Tasks + +The "size" is using the modified Fibonacci scale. A '1' is expected +to take less than an hour. A '3' is expected to take 3-6 hours. A +'5' should take the better part of a day or two. An 8 should take +several days. + ++---------------------------------------------------------+------+------------+----+ +| Tasks Implemented and Working | Size | Person | US | ++=========================================================+======+============+====+ +| [Intelligent Error Handling](#error-hand) | 3 | Andrew | 3 | ++---------------------------------------------------------+------+------------+----+ +| [Search](#search) | 5 | Tomer | 6 | ++---------------------------------------------------------+------+------------+----+ +| [Email verification](#email-verify) | 8 | Luke | 2 | ++---------------------------------------------------------+------+------------+----+ +| [Alternate Scoring and pairing methods](#alt-score-par) | 5 | G, A, D | 7,8| ++---------------------------------------------------------+------+------------+----+ +| [Asynchronous Riot Pulls](#async) | 5 | Nathaniel | 11 | ++---------------------------------------------------------+------+------------+----+ +| [Map out brackets scaffolding](#brack-scaff) | 5 | Tomer | 10 | ++---------------------------------------------------------+------+------------+----+ +| [Create braket creation and submission gui](#brack-gui) | 3 | Tomer | 10 | ++---------------------------------------------------------+------+------------+----+ +| [General Interface Cleanups](#interface-clean) | 2 | Tomer | 1 | ++---------------------------------------------------------+------+------------+----+ +| [Make it look professional](#professional) | 3 | All | 1 | ++---------------------------------------------------------+------+------------+----+ +| [Expand Peer Evaluation](#peer-expansion) | 3 | G, A, D | 7 | ++---------------------------------------------------------+------+------------+----+ +| [Private Messages](#priv-messages) | 5 | N, L | 5 | ++---------------------------------------------------------+------+------------+----+ +| [Alerts](#alerts) | 3 | Guntas | 4 | ++---------------------------------------------------------+------+------------+----+ + + ++---------------------------------------------------------+------+------------+----+ +| Tasks Implemented and Not Working Well | Size | Person | US | ++=========================================================+======+============+====+ +| [Remote Game UserNames](#remote_user) | 3 | Davis | 12 | ++---------------------------------------------------------+------+------------+----+ +| [Tournament preference interface](#tourn-prefer) | 3 | Andrew | 9 | ++---------------------------------------------------------+------+------------+----+ +| [More types of seeded settings](#seed) | 2 | Andrew | 9 | ++---------------------------------------------------------+------+------------+----+ +| [Project Leaguer Logo](#logo) | spike| G, D | 1 | ++---------------------------------------------------------+------+------------+----+ + + ++---------------------------------------------------------+------+------------+----+ +| Tasks Not Implemented | Size | Person | US | ++=========================================================+======+============+====+ +| [None](#success) | 0 | -- | 0 | ++---------------------------------------------------------+------+------------+----+ + +[How to improve](#improve) + +# Implemented and working + +## Intelligent Error Handling {#error-hand} + +Several important cases for error redirection were handled via +standard permissions changes and in the end only a few specific +redirections needed to be coded directly (such as correctly handling +redirections away from a destoryed tournament or match). + +## Search {#search} + +A basic SearchController and simple view were implemented. The search +controller took the query, and queried the "name" columns in the user +and tournament database tables. An "advanced" search mode allows +filtering tournaments by game type. We would like to be able to +search based on other parameters and settings of the tournament, and +other user attributes. + +To more easily render the search results, we moved the tournament and +player display blocks from their respective index pages to be shared, +to display them consistently, and avoid code duplication. + +## Email verification {#email-verify} + +Email verification was (finally) implemented. It uses delayed_job +(see [asynchronous Riot pulls](#async)) to avoid blocking the page if +the MX isn't responding. However, email password resets are not yet +implemented, which is the obvious use-case for verified emails. + +## Alternate Scoring and pairing methods {#alt-score-par} + +We overhaulted the entire tournament structure and introduced a +modular/pluggable system for seeding, scheduling, sampling, and +scoring, lovingly called the 4S-Module System. We relocated code from +other places into these modules in the 'lib' directory including form +HTML which is retrieved dynamically from these modules. In the case +of sampling (retrieving and populating statistics for scoring) we +built an intelligent system for populating available modules for a +game-type based on the statistics needed for its scoring methods which +replaced their manual configuration. We introduced Tournament Stages +to accomodate a wider range of tournament types and modes and designed +the library modules to be general enough to use results of past stages +or player statistics to affect future ones. + +## More types of seeded settings {#seed} + +We implemented seeding for initial placement of players within a tournament as part +of the 4S-Module System. Three seeding modules were implemented. + + 1. Early bird - Team rosters are based on order of registration for + the tournament. + 2. Random - Team rosters are randomized. + 3. Fair Ranked - Players are distributed evenly on teams according to + their performance in the previous tournament stage. This method + only works for tournaments with multiple stages. + +## Asynchronous Riot Pulls {#async} + +Prior to this iteration, calls to the Riot Games API here hard-coded, +and blocked the page from loading while the requests were made. We +needed to (1) create an interface for doing this flexibly (2) make the +calls asynchrounous to avoid blocking the page load. To perform the +asynchronous requets, we used the "delayed_job" gem to run each +request as a separate job process. A challenge with this was that +most API's (including Riot Games') place an artifical limit on API +requests within a given period of time (in the case of Riot Games, 10 +requests per 10 seconds, and 500 per 10 minutes). So the background +job making requests must be throttled from making too many within +several given rolling blocks of unit time. To do this, we implemented +a ThrottledApiRequest base class that makes it simple to create +throttled asynchronous API requests, which we used to create the +"RiotAPI" sampling method, but is applicable in a much wider variety +of situations. + +## Map out brackets scaffolding {#brack-scaff} + +Brackets are structures that have a users' prediction of the winners +of a tournaments matches. Essentially, a tournament has many brackets, +each bracket has a user that creates it, and each bracket has +bracket-matches that correspond to the matches of the tournament the +bracket belongs to. Bracket matches are only models, as the user +should be able to predit all winners from a single view that belongs +to a bracket. Brackets on the other hand have a model, controller, and +views, so that users may create, edit, and view them. + +## Create braket creation and submission gui {#brack-gui} + +The bracket creation gui looks simple to the user, but does a lot on +the backend. When the user presses "Make a bracket" on a tournament, +a bracket is created based on the user, the tournament, and the +tournament's matches. The bracket's submission GUI looks a lot like an +elimination tournament's SVG, however the user is able to click on +teams to advance them forward. The SVG has javascript functions that +both advance the teams visually on the SVG, and write the user's +prediction in a hidden form. When the user clicks submit, the +predictions are saved in the bracket's matches. + +## General Interface Cleanups {#interface-clean} + +Project Leaguer better handled tournament interface in this iteration. +Tournaments are listed more cleanly on the index page. Each game type +has an icon listed with it to better identify different game types on +the index page. Each tournament's host's gravatar is also listed on +the index page. Creating a tournament itself is also cleaner. +Customization categories are clearly separated and use the correct +selection or input types for easy use. + +## Make it look professional {#professional} + +The team decided on a color scheme for Leaguer during this +sprint. This scheme was applied to every page in the site. Since +e-sport players often spend hours in front of screens, it was +important to reduce eye strain by making our interface dark while +keeping it sleek and modern. We implemented Gravatar in a few more +spots as well, helping to distinguish between users more easily. he +default image was changed to give each user a unique avatar even if +they've not set one. Tournament creation and listing also received +tune ups, with images listed with each tournament to help display its +game type and a better creation page when creating a tournament. + +## Expand Peer Evaluation {#peer-expansion} + +We created a scoring modules for users to select the preferred scoring +method and preferred peer evaluation for users to choose from when +creating a tournament. The peer evaluation modules calculate the +score correctly and grab the statistics from the submission forms. The +skeletons for three such scoring methods namely, winnerTakesAll, +FibonacciPeerWithBlowout, MarginalPeer, have been created. For the +MarginalPeer we do not have a view but we do have the methods that +would calculate the score provided the stats are in the database. For +WinnerTakesAll and FibonacciWithPeerBlowout we do have views and data +being collected from the interface and used to calculate score. + +## Private Messages {#priv-messages} + +Private Messsaging in Project Leaguer is possible between two +registered users. Project Leaguer uses the gem 'Mailboxer' to achieve +private messaging. A user is able to interact with the private +messaging system by clicking on the "Messages" located in the header +toolbar at the top of every page. This results in the index page, +which lists all unread and read conversations. You can then click on a +conversation to view all of its messages and from there you can also +reply. Creating a new message is as simple as: click the "start a new +conversation", list the user you wish to pm with, write the +conversation's subject, and write the message itself. + +## Alerts {#alerts} + +The alerts system was implemented with the help of the 'Mailboxer' gem +which is the same as the private message system. The Alert system was +made available to anyone who had permissions to create an alert and +all users were notified when an alert was created with a live update, +a pop up notification which redirects to the list of alerts, in the +navigation bar of the recieving users. The alerts icon appeared only +when there is a new alert. + +# Implemented but not working well + +## Expand Peer Evaluation {#peer-expansion} + +We created scoring modules for users to select the preferred scoring +method, including options for peer review, during tournament +creation. We created three scoring modules: winnerTakesAll, +FibonacciPeerWithBlowout, and MarginalPeer, two of which work. Since +winnerTakesAll and FibonacciPeerWithBlowout demonstrated the extremes, +the testing of MarginalPeer was considered low priority. As a whole +the scoring modules' interface, outlined in the 4S-Module README for +scoring was designed much better than than our implementations were +able to show off by the end of the sprint. + +## Remote Game UserNames {#remote_user} + +Project Leaguer stores for each user and game-type a reference to a +remote username. This allows a user to register her accounts with the +system and for API requests to be completed for a registered +game-type. The interface for this was only partially completed, and +being integrated into a player's profile page directly, it only +allowed registration of League of Legends remote usernames. + +## Project Leaguer Logo {#logo} + +While a suitable logo was created, it was deemed too unprofessional +for use by the project at this time. The search for a more amicable +logo remains underway. + +## Tournament preference interface {#tourn-prefer} + +Tournament Settings are handled correctly and securely, the permission +system is robust enough to handle custom preferences, the database +structure exists to handle them, and even the icons for adding them +were created, however, the interface for adding them was deemed low +priority for this sprint in comparison to the lib modules and +permissions overhauls. + +# Not implemented {#success} + +We implemented everything we planned to, though much of it was not +working in time for the demonstration on Tuesday. + +# How to improve {#improve} + +1. In this sprint our primary mistake was in planning. We were too + nonspecific in deciding what to get done and accepted too many + tasks as essential. We should have scaled down our efforts and + focused on only the most essential tasks. In the future, more time + will be spent on designing and planning sprints and dividing + workload. + +2. Also because of last-minute alterations and refactors, nearly + EVERYTHING that could have gone wrong in our presentation went + wrong (the wifi connectivity dropped, our presentation server on + demo.projectleaguer.net wasn't updating, even Andrew's window + manager segfaulted). Much of this could have been fixed if we + simply a little better prepared and had frozen our codebase a + specified time prior to the presentation. Realistically, things + would have worked phenomenally better had we another few hours to + prepare for the presentation. In the future when presenting to + potential users or stakeholders we will be sure to have a practiced + presentation to match the polished codebase which will then be able + to speak for itself. + +3. We can better plan our project's flow and layout. We refactored a + lot of code in this sprint because different methods didn't work + together as we thought they would. We spent a lot of time + reworking things that should have already been complete simply + because of incomptability issues; the transitions between steps in + our flow didn't function as we intended. Moving forward, we have + more time to ensure that components work together properly and are + tested in a more comprehensive manner. diff --git a/doc/Sprint3.md b/doc/Sprint3.md new file mode 100644 index 0000000..31f505e --- /dev/null +++ b/doc/Sprint3.md @@ -0,0 +1,80 @@ +--- +title: "Team 6 - Project Leaguer: Sprint 3" +author: [ Nathaniel Foy, Guntas Grewal, Tomer Kimia, Andrew Murrell, Luke Shumaker, Davis Webb ] +--- + +# User Stories + +1) As a user, I would like the web interface to look more professional and complete. + +2) As an admin, I would like to have an email verification system for a more secure + and spam free enviroment. + +3) As a user, I would like intelligent error handling (e.g. 404 and 403 redirection). + +4) As an admin, I would like to send alerts to users. + +5) As a user, I would like to be able to send private messages. + +6) As a user, I would like a working search utility. + +7) As a host or player, I would like customizable settings for peer evaluation and + scoring. + +8) As a host, I would like to have multiple tournament structures and types for + pairing and running tournaments. (e.g. Round Robin team pairings). + +9) As a host, I would like to have an interface for adding tournament-specific + preferences (e.g. Capture the Teemo). + +10) As a user, I would like to view and create brackets. + +11) As a user, I would like the Riot API to be asynchronously polled in the + background so League of Legends tournaments proceed automatically. + +12) As a player, I would like a way to enter my usernames for several different + remote games. + + +#Tasks + +The "size" is using the modified Fibonacci scale. A '1' is expected +to take less than an hour. A '3' is expected to take 3-6 hours. A +'5' should take the better part of a day or two. An 8 should take +several days. + ++---------------------------------------------------------+------+------------+----+ +| Task Description | Size | Person | US | ++=========================================================+======+============+====+ +| Intelligent Error Handling (404 redirection) | 3 | Andrew | 3 | ++---------------------------------------------------------+------+------------+----+ +| Search | 5 | Tomer | 6 | ++---------------------------------------------------------+------+------------+----+ +| Remote Game UserNames | 3 | Davis | 12 | ++---------------------------------------------------------+------+------------+----+ +| Email verification | 8 | Luke | 2 | ++---------------------------------------------------------+------+------------+----+ +| Alternate Scoring and pairing methods | 5 | G, A, D | 7,8| ++---------------------------------------------------------+------+------------+----+ +| Tournament preference interace | 3 | Andrew | 9 | ++---------------------------------------------------------+------+------------+----+ +| More types of seeded settings | 2 | Andrew | 9 | ++---------------------------------------------------------+------+------------+----+ +| Asynchronous Riot Pulls | 5 | Nathaniel | 11 | ++---------------------------------------------------------+------+------------+----+ +| Map out brackets scaffolding | 5 | Tomer | 10 | ++---------------------------------------------------------+------+------------+----+ +| Create braket creation and submission gui | 3 | Tomer | 10 | ++---------------------------------------------------------+------+------------+----+ +| General Interface Cleanups | 2 | Tomer | 1 | ++---------------------------------------------------------+------+------------+----+ +| Make it look professional | 3 | All | 1 | ++---------------------------------------------------------+------+------------+----+ +| Expand Peer Evaluation | 3 | G, A, D | 7 | ++---------------------------------------------------------+------+------------+----+ +| Private Messages | 5 | N, L | 5 | ++---------------------------------------------------------+------+------------+----+ +| Alerts | 3 | Guntas | 4 | ++---------------------------------------------------------+------+------------+----+ +| Project Leaguer Logo | spike| G, D | 1 | ++---------------------------------------------------------+------+------------+----+ diff --git a/doc/sprint2Retro.md b/doc/sprint2Retro.md new file mode 100644 index 0000000..1a40bac --- /dev/null +++ b/doc/sprint2Retro.md @@ -0,0 +1,12 @@ +Riot API
+
+The Riot API allowed us to pull information from the Riot servers for League of Legends
+Tournaments. For sprint 2, we were able to pull the information, but not very efficiently.
+We ran into an issue where we were making too many pull requests for our Riot developer key
+which would gave us some issues.
+
+Anti-Spam
+
+For now, the only anti-spam protection that we have is a captcha at the user creation page.
+Our hopes is to implement an email verification system in order to help further protect
+Project Leaguer from spam.
diff --git a/lib/sampling/README.md b/lib/sampling/README.md new file mode 100644 index 0000000..e4b3fbf --- /dev/null +++ b/lib/sampling/README.md @@ -0,0 +1,50 @@ +Sampling interface +================== + +Files in this directory should be _classes_ implementing the following +interface: + + - `self.works_with?(Game) => Boolean` + + Returns whether or not this sampling method works with the + specified game. + + - `self.can_get?(String setting_name) => Fixnum` + + Returns whether or not this sampling method can get a specifed + statistic; 0 means 'false', positive integers mean 'true', where + higher numbers are higher priority. + + - `self.uses_remote?() => Boolean` + + Return whether or not this sampling method requires remote IDs for + users. + + - `self.set_remote_name(User, Game, String)` + + Set the remote ID for a user for the specified game. It is safe to + assume that this sampling method `works_with?` that game. + + - `self.get_remote_name(Object)` + + When given an object from `RemoteUsername#value`, give back a + human-readable/editable name to display + +---- + + - `initialize(Match)` + + Construct new Sampling object for the specified match. + + - `start()` + + Begin fetching the statistics. + + - `render_user_interaction(User) => String` + + Returns HTML to render on a page. + + - `handle_user_interaction(User, Hash params)` + + Handles params from the form generated by + `#user_interaction_render`. diff --git a/lib/sampling/double_blind.rb.bak b/lib/sampling/double_blind.rb.bak new file mode 100644 index 0000000..6a30d57 --- /dev/null +++ b/lib/sampling/double_blind.rb.bak @@ -0,0 +1,35 @@ +module Sampling + module DoubleBlind + def self.works_with?(game) + return true + end + + def can_get?(setting_name) + return 1 + end + + def self.uses_remote? + return false + end + + def self.set_remote_name(user, game, value) + raise "This sampling method doesn't use remote usernames." + end + + def self.get_remote_name(value) + raise "This sampling method doesn't use remote usernames." + end + + def self.sampling_start(match, statistics) + # TODO + end + + def self.render_user_interaction(match, user) + # TODO + end + + def self.handle_user_interaction(match, user, sampling_params) + # TODO + end + end +end diff --git a/lib/sampling/manual.html.erb b/lib/sampling/manual.html.erb new file mode 100644 index 0000000..2bbd6da --- /dev/null +++ b/lib/sampling/manual.html.erb @@ -0,0 +1,31 @@ +<% if @tournament.hosts.include? @current_user %> + <fieldset><legend>Winner</legend> + <ul> + <% @match.teams.each do |team| %> + <li><label> + <input type="radio" name="manual[winner]" value="<%= team.id %>"> + Team <%= team.id %> + </label></li> + <% end %> + </ul> + </fieldset> + <% @match.teams.each do |team| %> + <fieldset><legend>Statistics for Team <%= team.id %></legend> + <% team.users.each do |user| %> + <fieldset><legend><%= user.name %></legend> + <% @stats.reject{|s|s=="win"}.each do |stat| %> + <p> + <label> + <%= stat.titleize %> + <input type="numeric" name="manual[statistics][<%= user.id %>][<%= stat %>]"> + </label> + </p> + <% end %> + </fieldset> + <% end %> + </fieldset> + <% end %> + <input type="submit", value="Finish match" > +<% else %> + <p>The match is running; the host has yet to post the scores of the match.</p> +<% end %> diff --git a/lib/sampling/manual.rb b/lib/sampling/manual.rb new file mode 100644 index 0000000..853516c --- /dev/null +++ b/lib/sampling/manual.rb @@ -0,0 +1,62 @@ +module Sampling + class Manual + def self.works_with?(game) + return true + end + + def self.can_get?(setting_name) + return 1 + end + + def self.uses_remote? + return false + end + + def self.set_remote_name(user, game, value) + raise "This sampling method doesn't use remote usernames." + end + + def self.get_remote_name(value) + raise "This sampling method doesn't use remote usernames." + end + + #### + + def initialize(match) + @match = match + end + + def start + # do nothing + end + + def render_user_interaction(user) + @tournament = @match.tournament_stage.tournament + @current_user = user + @stats = @match.stats_from(self.class) + + require 'erb' + erb_filename = File.join(__FILE__.sub(/\.rb$/, '.html.erb')) + erb = ERB.new(File.read(erb_filename)) + erb.filename = erb_filename + return erb.result(binding).html_safe + end + + def handle_user_interaction(user, params) + # => Save sampling_params as statistics + if (@match.tournament_stage.tournament.hosts.include? user) + manual_params = params.require(:manual) + winner = Team.find(manual_params[:winner]) + @match.users.each do |user| + Statistic.create(match: @match, user: user, + name: "win", value: winner.users.include?(user)) + @match.stats_from(self.class).reject{|s|s=="win"}.each do |stat| + Statistic.create(match: @match, user: user, + name: stat, value: manual_params[:statistics][user.id][stat].to_i) + end # stats + end # users + end # permission + end # def + + end +end diff --git a/lib/sampling/peer_review.html.erb b/lib/sampling/peer_review.html.erb new file mode 100644 index 0000000..a0b9c4d --- /dev/null +++ b/lib/sampling/peer_review.html.erb @@ -0,0 +1,28 @@ +<% if @feedbacks_missing.include? @user %> + <script type="text/javascript"> + function score_peers() { + var list = $('ol#peer_review_boxes'); + for(var i=0, var len=list.length; i < len; i++) { + if ( i == len-1) { + comma = ""; + } + $('peer_review').value += $('ol#peer_review_boxes:eq(' + i + ')').text() + comma; + } + } + </script> + <input type="hidden" id="peer_review" name="peer_review" value="" /> + <ol id="peer_review_boxes" class="sortable"> + <% @team.users.reject{|u|u==@user}.each do |user| %><li> + <%= user.user_name %> + <br> + <%# TODO: display more statistics %> + </li><% end %> + </ol> + <input type="submit" value="Submit peer evaluation", onsubmit="score_peers()") > +<% else %> + <p>Still waiting for peer feedback from the following users: + <ul><% @feedbacks_missing.each do |user| %> + <li><%= link_to user %></li> + <% end %></ul> + </p> +<% end %> diff --git a/lib/sampling/peer_review.rb b/lib/sampling/peer_review.rb new file mode 100644 index 0000000..7faa241 --- /dev/null +++ b/lib/sampling/peer_review.rb @@ -0,0 +1,91 @@ +module Sampling + class PeerReview + def self.works_with?(game) + return true + end + + def self.can_get?(setting_name) + return setting_name.start_with?("review_from_") ? 2 : 0 + end + + def self.uses_remote? + return false + end + + def self.set_remote_name(user, game, value) + raise "This sampling method doesn't use remote usernames." + end + + def self.get_remote_name(value) + raise "This sampling method doesn't use remote usernames." + end + + #### + + def initialize(match) + @match = match + end + + def start + # do nothing + end + + def render_user_interaction(user) + @user = user + @team = get_team(match) + @reviews_missing = get_reviews_missing(match) + + require 'erb' + erb_filename = File.join(__FILE__.sub(/\.rb$/, '.html.erb')) + erb = ERB.new(File.read(erb_filename)) + erb.filename = erb_filename + return erb.result(binding).html_safe + end + + def handle_user_interaction(reviewing_user, params) + i = 0 + params[:peer_review].to_s.split(',').each do |user_name| + reviewed_user = User.find_by_user_name(user_name) + reviewed_user.statistics.create(match: @match, name: "review_from_#{reviewing_user.user_name}", value: i) + i += 1 + end + end + + private + + def self.get_users(match) + users = [] + match.teams.each{|t| users.concat(t.users)} + return users + end + + def self.get_team(match) + match.teams.find{|t|t.users.include?(@user)} + end + + def self.get_reviews(match) + ret = {} + match.statistiscs.where("'name' LIKE 'review_from_%'").each do |statistic| + ret[statistic.user] ||= {} + ret[statistic.user][User.find_by_user_name(statistic.name.sub(/^review_from_/,''))] = statistic.value + end + return ret + end + + def self.get_reviews_missing(match) + require 'set' + ret = Set.new + + review = get_reviews(match) + users = get_users(match) + + review.each do |review| + (users - review.keys).each do |user| + ret.add(user) + end + end + + return ret + end + end +end diff --git a/lib/sampling/riot_api.rb b/lib/sampling/riot_api.rb new file mode 100644 index 0000000..4e72f91 --- /dev/null +++ b/lib/sampling/riot_api.rb @@ -0,0 +1,207 @@ +module Sampling + class RiotApi + protected + def self.api_name + "prod.api.pvp.net/api/lol" + end + + protected + def self.api_key + ENV["RIOT_API_KEY"] + end + + protected + def self.region + ENV["RIOT_API_REGION"] + end + + protected + def self.url(request, args={}) + "https://prod.api.pvp.net/api/lol/#{region}/#{request % args.merge(args){|k,v|url_escape(v)}}?api_key=#{api_key}" + end + + protected + def self.url_escape(string) + URI::escape(string.to_s, /[^a-zA-Z0-9._~!$&'()*+,;=:@-]/) + end + + protected + def self.standardize(summoner_name) + summoner_name.to_s.downcase.gsub(' ', '') + end + + protected + def self.stats_available + ["win", "numDeaths", "turretsKilled", "championsKilled", "minionsKilled", "assists"] + end + + protected + class Job < ThrottledApiRequest + def initialize(request, args={}) + @url = Sampling::RiotApi::url(request, args) + limits = [ + {:unit_time => 10.seconds, :requests_per => 10}, + {:unit_time => 10.minutes, :requests_per => 500}, + ] + super(RiotApi::api_name, limits) + end + + def perform + response = open(@url) + status = response.status + data = JSON::restore(response.read) + + # Error codes that RIOT uses: + # "400"=>"Bad request" + # "401"=>"Unauthorized" + # "429"=>"Rate limit exceeded" + # "500"=>"Internal server error" + # "503"=>"Service unavailable" + # "404"=>"Not found" + # Should probably handle these better + if status[0] != "200" + raise "GET #{@url} => #{status.join(" ")}" + end + return self.handle(data) + end + + def handle(data) + return true + end + end + + ######################################################################## + + ## + # Return whether or not this sampling method works with the specified game. + # Spoiler: It only works with League of Legends (or subclasses of it). + public + def self.works_with?(game) + if api_key.nil? or region.nil? + return false + end + if game.name == "League of Legends" + return true + end + unless game.parent.nil? + return works_with?(game.parent) + end + end + + ## + # Return whether or not the API can get a given statistic for + # a given user. + public + def self.can_get?(stat) + if stats_available.include?(stat) + return 2 + else + return 0 + end + end + + ## + # This sampling method uses remote IDs + public + def self.uses_remote? + return true + end + + ## + # When given a summoner name for a user, figure out the summoner ID. + public + def self.set_remote_name(user, game, summoner_name) + Delayed::Job.enqueue(UsernameJob.new(user, game, summoner_name), :queue => RiotApi::api_name) + end + protected + class UsernameJob < Job + def initialize(user, game, summoner_name) + @user_id = user.id + @game_id = game.id + # Escape any funny stuff + summoner_names = [summoner_name].map{|name|Sampling::RiotApi::standardize(name.gsub(',',''))} + # Generate the request + super("v1.3/summoner/by-name/%{summonerNames}", { :summonerNames => summoner_names.join(",") }) + end + def handle(data) + user = User.find(@user_id) + game = Game.find(@game_id) + + standardized_summoner_name = data.keys.first + remote_data = { + :id => data[standardized_summoner_name]["id"], + :name => data[standardized_summoner_name]["name"], + } + + user.set_remote_username(game, remote_data) + end + end + + ## + # When given data from RemoteUsername#value, give back a readable name to display. + # Here, this is the summoner name. + public + def self.get_remote_name(data) + data["name"] + end + + #### + + public + def initialize(match) + @match = match + end + + ## + # Fetch all the statistics for a match. + public + def start + @match.teams.each do |team| + team.users.each do |user| + #For demo purposes, we are hard coding in a league of legends game id. + Delayed::Job.enqueue(FetchStatisticsJob.new(user, @match, @match.stats_from(self.class), 10546), :queue => RiotApi::api_name) + end + end + end + protected + class FetchStatisticsJob < Job + def initialize(user, match, stats, last_game_id) + @user_id = user.id + @match_id = match.id + @stats = stats + @last_game_id = last_game_id + + # Get the summoner id + summoner = user.get_remote_username(match.tournament_stage.tournament.game) + # Generate the request + super("v1.3/game/by-summoner/%{summonerId}/recent", { :summonerId => summoner["id"] }) + end + def handle(data) + user = User.find(@user_id) + match = Match.find(@match_id) + if @last_game_id.nil? + Delayed::Job.enqueue(FetchStatisticsJob.new(user, match, data["games"][0]["gameId"]), :queue => RiotApi::api_name) + else + if @last_game_id == data["games"][0]["gameId"] + sleep(4.minutes) + Delayed::Job.enqueue(FetchStatisticsJob.new(user, match, @last_game_id), :queue => RiotApi::api_name) + else + @stats.each do |stat| + Statistic.create(user: user, match: match, name: stat, value: data["games"][0]["stats"][stat]) + end + end + end + end + end + + public + def render_user_interaction(user) + return "" + end + + public + def handle_user_interaction(user) + # do nothing + end + end +end diff --git a/lib/scheduling/README.md b/lib/scheduling/README.md new file mode 100644 index 0000000..fe6aba1 --- /dev/null +++ b/lib/scheduling/README.md @@ -0,0 +1,22 @@ +Scheduling interface +==================== + +Files in this directory should be _classes_ implementing the following +interface: + + - `initialize(TournamentStage)` + + Construct new Scheduling object from tournament_stage. + + - `create_matches` + + Creates all the matches of the current round. + + - `finish_match(Match)` + + Progresses the match through the schedule. + + - `graph` + + Returns a string representation of an svg image of the current + stage. diff --git a/lib/scheduling/elimination.rb b/lib/scheduling/elimination.rb new file mode 100644 index 0000000..a2ff989 --- /dev/null +++ b/lib/scheduling/elimination.rb @@ -0,0 +1,146 @@ + +module Scheduling + class Elimination + include Rails.application.routes.url_helpers + + def initialize(tournament_stage) + @tournament_stage = tournament_stage + end + + + def create_matches + num_teams = (tournament.players.count/tournament.min_players_per_team).floor + num_matches = (Float(num_teams - tournament.min_teams_per_match)/(tournament.min_teams_per_match - 1)).ceil + 1 + for i in 1..num_matches + tournament_stage.matches.create + end + + match_num = num_matches-1 + team_num = 1 + + tournament.players.shuffle + + # for each grouping of min_players_per_team + tournament.players.each_slice(tournament.min_players_per_team) do |team_members| + # create a new team in the current match + tournament_stage.matches.order(:id)[match_num].teams.push(Team.create(users: team_members)) + + # if the match is full, move to the next match, otherwise move to the next team + if (team_num == tournament.min_teams_per_match) + tournament_stage.matches[match_num].update(status: 1); + match_num -= 1 + team_num = 1 + else + team_num += 1 + end + end + end + + def finish_match(match) + logBase = match.tournament_stage.tournament.min_teams_per_match + matches = match.tournament_stage.matches_ordered + cur_match_num = matches.invert[match] + unless cur_match_num == 1 + match.winner.matches.push(matches[(cur_match_num+logBase-2)/logBase]) + end + if matches[(cur_match_num+logBase-2)/logBase].teams.count == match.tournament_stage.tournament.min_teams_per_match + matches[(cur_match_num+logBase-2)/logBase].update(status: 1) + end + end + + def graph(current_user) + matches = @tournament_stage.matches_ordered + numTeams = @tournament_stage.tournament.min_teams_per_match + logBase = numTeams + + # depth of SVG tree + depth = Math.log(matches.count*(logBase-1),logBase).floor+1; + + # height of SVG + matchHeight = 50*logBase; + height = [(matchHeight+50) * logBase**(depth-1) + 100, 500].max; + height = height/2; + + str = <<-STRING + <svg version="1.1" baseProfile="full" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + width="100%" height="#{height}"> + <defs> + <radialGradient id="gradMatch" cx="50%" cy="50%" r="80%" fx="50%" fy="50%"> + <stop offset="0%" style="stop-color:#fff; stop-opacity:1" /> + <stop offset="100%" style="stop-color:#ccc;stop-opacity:0" /> + </radialGradient> + </defs> + STRING + base = 1 + pBase = 1 + (1..matches.count).each do |i| + matchDepth = Math.log(i*(logBase-1), logBase).floor+1 + if matchDepth > Math.log(base*(logBase-1), logBase).floor+1 + pBase = base + base = i + end + rh = 100 / (logBase**(depth-1)+1) - 100/height; + rw = 100/(depth+1) - 5 + rx = 50/(depth+1) + 100/(depth+1)*(depth-matchDepth) + ry = 100/(logBase**(matchDepth-1)+1) * (i-base+1) - rh/2 + + str += "\t<a id=\"svg-match-#{i}\" xlink:href=\"#{match_path(matches[i])}\"><g>\n" + str += "\t\t<rect height=\"#{rh}%\" width=\"#{rw}%\" x=\"#{rx}%\" y=\"#{ry}%\" fill=\"url(#gradMatch)\" rx=\"5px\" stroke-width=\"2\"" + case matches[i].status + when 0 + if matches[i].teams.count == 0 + str += ' stroke="red"' + str += ' fill-opacity="0.6"' + else + str += 'stroke="orange"' + end + when 1 + str += ' stroke="green"' + when 2 + str += ' stroke="lightblue"' + when 3 + str += ' stroke="grey"' + end + + str += "/>\n" + + t = 1 + while t <= numTeams + color = (matches[i].teams[t-1] and matches[i].teams[t-1].users.include?(current_user)) ? "#5BC0DE" : "white" + str += "\t\t<rect width=\"#{rw-5}%\" height=\"#{rh*Float(30)/(matchHeight)}%\" x=\"#{rx + 2.5}%\" y=\"#{ry + (Float(t-1)/numTeams)*rh + 1 }%\" fill=\"#{color}\" />\n" + if matches[i].teams[t-1] + str += "\t\t<text x=\"#{rx + rw/4}%\" y=\"#{ry + (Float(t-1)/numTeams + Float(33)/(matchHeight))*rh}%\" font-size=\"120%\">Team #{matches[i].teams[t-1].id}</text>\n" + end + if (t < numTeams) + str += "\t\t<text x=\"#{rx + 1.3*rw/3}%\" y=\"#{ry + (Float(t)/numTeams)*rh + 1}%\" font-size=\"120%\"> VS </text>\n" + end + t = t + 1 + end + + if i > 1 + parent = (i+logBase-2)/logBase + pDepth = Math.log(parent*(logBase-1), logBase).floor+1 + lastrx = 50/(depth+1) + 100/(depth+1)*(depth-pDepth) + lastry = 100/(logBase**(pDepth-1)+1) * (parent-pBase+1) - rh/2 + str += "\t\t<line x1=\"#{rx+rw}%\" y1=\"#{ry+rh/2}%\" x2=\"#{lastrx}%\" y2=\"#{lastry+rh/2}%\" stroke=\"white\" stroke-width=\"2\" >\n" + end + str += "</g></a>\n" + end + str += '</svg>' + + return str + end + + private + + def tournament_stage + @tournament_stage + end + + def tournament + tournament_stage.tournament + end + end +end diff --git a/lib/scheduling/round_robin.rb b/lib/scheduling/round_robin.rb new file mode 100644 index 0000000..7ee617d --- /dev/null +++ b/lib/scheduling/round_robin.rb @@ -0,0 +1,70 @@ +# http://stackoverflow.com/questions/6648512/scheduling-algorithm-for-a-round-robin-tournament +module Scheduling + class RoundRobin + include Rails.application.routes.url_helpers + + def initialize(tournament_stage) + @tournament_stage = tournament_stage + end + + def create_matches + # => find the number of matches and teams to create + @num_teams = (tournament.players.count/tournament.min_players_per_team).floor + @matches_per_round = (@num_teams / tournament.min_teams_per_match).floor + + # => initialize data and status members + @team_pairs ||= Array.new + if @team_pairs.empty? + @matches_finished = 0 + end + + # => Create new matches + @matches_per_round.times do + tournament_stage.matches.create + end + + # => seed the first time + if @team_pairs.empty? + tournament_stage.seeding.seed(tournament_stage) + tournament_stage.matches.each {|match| match.teams.each {|team| @team_pairs.push team}} + else + # => Reorder the list of teams + top = @team_pairs.shift + @team_pairs.push @team_pairs.shift + @team_pairs.unshift top + + # => Add the teams to the matches + match = tournament_stage.matches[@matches_finished-1] + matches = 1 + (0..@team_pairs.count-1).each do |i| + match.teams += @team_pairs[i] + if @team_pairs.count.%(tournament.min_teams_per_match).zero? + match = tournament_stage.matches[@matches_finished-1 + matches] + matches += 1 + end + end + + end + + # => Set the match statuses to ready (1) + tournament_stage.matches.each {|match| match.update(status: 1)} + + end + + def finish_match(match) + @matches_finished += 1 + end + + def graph(current_user) + end + + private + def tournament_stage + @tournament_stage + end + + def tournament + tournament_stage.tournament + end + end +end diff --git a/lib/scoring/README.md b/lib/scoring/README.md new file mode 100644 index 0000000..efdc3cc --- /dev/null +++ b/lib/scoring/README.md @@ -0,0 +1,15 @@ +Scoring interface +================= + +Files in this directory should be _modules_ implementing the following +interface: + + - `stats_needed(Match) => Array[]=String` + + Returns which statistics need to be collected for this scoring + algorithm. + + - `score(Match) => Hash[User]=Integer` + + User scores for this match, assuming statistics have been + collected. diff --git a/lib/scoring/fibonacci_peer_with_blowout.rb b/lib/scoring/fibonacci_peer_with_blowout.rb new file mode 100644 index 0000000..a13d76c --- /dev/null +++ b/lib/scoring/fibonacci_peer_with_blowout.rb @@ -0,0 +1,33 @@ +module Scoring + module FibonacciPeerWithBlowout + def self.stats_needed(match) + return ["votes", "win", "blowout"] + match.users.map{|u|"review_from_#{u.user_name}"} + end + + def self.score(match) + scores = {} + match.users.each do |user| + stats = user.statistics.where(match: match) + votes = 0 + match.users.each do |u| + votes += convert_place_to_votes stats.where(name: "review_from_#{u.user_name}").first.value + end + win = stats.where(name: "win" ).first.value + blowout = stats.where(name: "blowout").first.value + scores[user] = self.score_user(votes, win, blowout) + end + scores + end + + protected + + def self.score_user(votes, win, blowout) + fibonacci = Hash.new { |h,k| h[k] = k < 2 ? k : h[k-1] + h[k-2] } + fibonacci[votes+3] + (win ? blowout ? 12 : 10 : blowout ? 5 : 7) + end + + def self.convert_place_to_votes(place) + (place == 0 or place == 1) ? 1 : 0 + end + end +end diff --git a/lib/scoring/marginal_peer.rb b/lib/scoring/marginal_peer.rb new file mode 100644 index 0000000..f2c0272 --- /dev/null +++ b/lib/scoring/marginal_peer.rb @@ -0,0 +1,16 @@ +module Scoring + module MarginalPeer + def self.stats_needed(match) + return ["rating", "win"] + end + + def self.score(match) + scores = {} + match.users.each do |user| + stats = Statistic.where(user: user, match: match) + scores[user] = stats.where(name: "rating").first.value + end + scores + end + end +end diff --git a/lib/scoring/winner_takes_all.rb b/lib/scoring/winner_takes_all.rb new file mode 100644 index 0000000..6cffb28 --- /dev/null +++ b/lib/scoring/winner_takes_all.rb @@ -0,0 +1,16 @@ +module Scoring + module WinnerTakesAll + def self.stats_needed(match) + return ["win"] + end + + def self.score(match) + scores = {} + match.users.each do |user| + stats = Statistic.where(user: user, match: match) + scores[user] = stats.where(name: "win").first.value ? 1 : 0 + end + scores + end + end +end diff --git a/lib/seeding/.keep b/lib/seeding/.keep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/seeding/.keep diff --git a/lib/seeding/README.md b/lib/seeding/README.md new file mode 100644 index 0000000..d323b6d --- /dev/null +++ b/lib/seeding/README.md @@ -0,0 +1,10 @@ +Seeding interface +================= + +Files in this directory should be _modules_ implement the following +interface: + + - `seed(TournamentStage)` + + Take a tournament stage, assign players to teams and teams to + matches (matches must exist). diff --git a/lib/seeding/early_bird_seeding.rb b/lib/seeding/early_bird_seeding.rb new file mode 100644 index 0000000..bf7b3c2 --- /dev/null +++ b/lib/seeding/early_bird_seeding.rb @@ -0,0 +1,20 @@ +module Seeding + module EarlyBirdSeeding + def self.seed(tournament_stage) + matches = tournament_stage.matches + match = matches.first + match_num = 0 + teams = 0 + tournament_stage.tournament.players.each_slice(tournament_stage.tournament.min_players_per_team) do |slice| + if teams < tournament_stage.tournament.min_teams_per_match + match.teams.push Team.create(players: slice) + teams += 1 + else + match_num += 1 + match = matches[match_num] + teams = 0 + end + end + end + end +end diff --git a/lib/seeding/fair_ranked_seeding.rb b/lib/seeding/fair_ranked_seeding.rb new file mode 100644 index 0000000..870ebdd --- /dev/null +++ b/lib/seeding/fair_ranked_seeding.rb @@ -0,0 +1,43 @@ +module Seeding + module FairRankedSeeding + def self.seed(tournament_stage) + matches = tournament.current_stage.matches + match = matches.first + match_num = 0 + players_used = 0 + (tournament.players.count/tournament.min_players_per_team).floor.times do + match.teams.push Team.create() + end + best_first(tournament).each_slice(tournament.min_teams_per_match) do |slice| + (0..tournament.min_teams_per_match-1).each do |index| + match.teams[index].players += slice[index] + end + players_used += 1 + if players_used == tournament.min_players_per_team + match_num += 1 + match = matches[match_num] + players_used = 0 + end + end + end + + private + def self.best_first(tournament) + tournament.players.sort {|a, b| better(a, b, tournament) } + end + + def self.better(player1, player2, tournament) + ps1 = previous_score(player1, tournament) + ps2 = previous_score(player2, tournament) + ps1 <=> ps2 + end + + def self.previous_score(player, tournament) + score = tournament.statistics.where(match: player.matches.last, user: player, name: :score) + if score.nil? + return 0 + end + score + end + end +end diff --git a/lib/seeding/random_seeding.rb b/lib/seeding/random_seeding.rb new file mode 100644 index 0000000..ccdba11 --- /dev/null +++ b/lib/seeding/random_seeding.rb @@ -0,0 +1,20 @@ +module Seeding + module RandomSeeding + def self.seed(tournament_stage) + matches = tournament_stage.matches + match = matches.first + match_num = 0 + teams = 0 + tournament_stage.tournament.players.shuffle.each_slice(tournament_stage.tournament.min_players_per_team) do |slice| + if teams < tournament_stage.tournament.min_teams_per_match + match.teams.push Team.create(players: slice) + teams += 1 + else + match_num += 1 + match = matches[match_num] + teams = 0 + end + end + end + end +end diff --git a/lib/throttled_api_request.rb b/lib/throttled_api_request.rb new file mode 100644 index 0000000..c48a66d --- /dev/null +++ b/lib/throttled_api_request.rb @@ -0,0 +1,32 @@ +# limits is in the format: +# limits = [ +# {:unit_time => 10.seconds, :requests_per => 10}, +# {:unit_time => 10.minutes, :requests_per => 500}, +# ] +class ThrottledApiRequest < Struct.new(:api_name, :limits) + def before(job) + loop do + sleep_for = -1 + ActiveRecord::Base.transaction do + ApiRequest.create(:api_name => self.api_name) + self.limits.each do |limit| + recent_requests = ApiRequest. + where(:api_name => self.api_name). + where("updated_at > ?", Time.now.utc - limit[:unit_time]). + order(:updated_at) + if (recent_requests.count > limit[:requests_per]) + sleep_for = [sleep_for, Time.now.utc - recent_requests[recent_requests.count-limit[:requests_per]].updated_at].max + end + end + if sleep_for != -1 + raise ActiveRecord::Rollback + end + end + if sleep_for != -1 + sleep(sleep_for) + else + break + end + end + end +end diff --git a/vendor/assets/javascripts/coordinates.js b/vendor/assets/javascripts/coordinates.js new file mode 100644 index 0000000..e2f5bf2 --- /dev/null +++ b/vendor/assets/javascripts/coordinates.js @@ -0,0 +1,94 @@ +var Coordinates = { + ORIGIN : new Coordinate(0, 0), + + northwestPosition : function(element) { + var x = parseInt(element.style.left); + var y = parseInt(element.style.top); + + return new Coordinate(isNaN(x) ? 0 : x, isNaN(y) ? 0 : y); + }, + + southeastPosition : function(element) { + return Coordinates.northwestPosition(element).plus( + new Coordinate(element.offsetWidth, element.offsetHeight)); + }, + + northwestOffset : function(element, isRecursive) { + var offset = new Coordinate(element.offsetLeft, element.offsetTop); + + if (!isRecursive) return offset; + + var parent = element.offsetParent; + while (parent) { + offset = offset.plus( + new Coordinate(parent.offsetLeft, parent.offsetTop)); + parent = parent.offsetParent; + } + return offset; + }, + + southeastOffset : function(element, isRecursive) { + return Coordinates.northwestOffset(element, isRecursive).plus( + new Coordinate(element.offsetWidth, element.offsetHeight)); + }, + + fixEvent : function(event) { + event.windowCoordinate = new Coordinate(event.clientX, event.clientY); + } +}; + +function Coordinate(x, y) { + this.x = x; + this.y = y; +} + +Coordinate.prototype.toString = function() { + return "(" + this.x + "," + this.y + ")"; +} + +Coordinate.prototype.plus = function(that) { + return new Coordinate(this.x + that.x, this.y + that.y); +} + +Coordinate.prototype.minus = function(that) { + return new Coordinate(this.x - that.x, this.y - that.y); +} + +Coordinate.prototype.distance = function(that) { + var deltaX = this.x - that.x; + var deltaY = this.y - that.y; + + return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); +} + +Coordinate.prototype.max = function(that) { + var x = Math.max(this.x, that.x); + var y = Math.max(this.y, that.y); + return new Coordinate(x, y); +} + +Coordinate.prototype.constrain = function(min, max) { + if (min.x > max.x || min.y > max.y) return this; + + var x = this.x; + var y = this.y; + + if (min.x != null) x = Math.max(x, min.x); + if (max.x != null) x = Math.min(x, max.x); + if (min.y != null) y = Math.max(y, min.y); + if (max.y != null) y = Math.min(y, max.y); + + return new Coordinate(x, y); +} + +Coordinate.prototype.reposition = function(element) { + element.style["top"] = this.y + "px"; + element.style["left"] = this.x + "px"; +} + +Coordinate.prototype.equals = function(that) { + if (this == that) return true; + if (!that || that == null) return false; + + return this.x == that.x && this.y == that.y; +} diff --git a/vendor/assets/javascripts/drag.js b/vendor/assets/javascripts/drag.js new file mode 100644 index 0000000..9ba6746 --- /dev/null +++ b/vendor/assets/javascripts/drag.js @@ -0,0 +1,229 @@ +/* + * drag.js - click & drag DOM elements + * + * originally based on Youngpup's dom-drag.js, www.youngpup.net + */ + +var Drag = { + BIG_Z_INDEX : 10000, + group : null, + isDragging : false, + + makeDraggable : function(group) { + group.handle = group; + group.handle.group = group; + + group.minX = null; + group.minY = null; + group.maxX = null; + group.maxY = null; + group.threshold = 0; + group.thresholdY = 0; + group.thresholdX = 0; + + group.onDragStart = new Function(); + group.onDragEnd = new Function(); + group.onDrag = new Function(); + + // TODO: use element.prototype.myFunc + group.setDragHandle = Drag.setDragHandle; + group.setDragThreshold = Drag.setDragThreshold; + group.setDragThresholdX = Drag.setDragThresholdX; + group.setDragThresholdY = Drag.setDragThresholdY; + group.constrain = Drag.constrain; + group.constrainVertical = Drag.constrainVertical; + group.constrainHorizontal = Drag.constrainHorizontal; + + group.onmousedown = Drag.onMouseDown; + }, + + constrainVertical : function() { + var nwOffset = Coordinates.northwestOffset(this, true); + this.minX = nwOffset.x; + this.maxX = nwOffset.x; + }, + + constrainHorizontal : function() { + var nwOffset = Coordinates.northwestOffset(this, true); + this.minY = nwOffset.y; + this.maxY = nwOffset.y; + }, + + constrain : function(nwPosition, sePosition) { + this.minX = nwPosition.x; + this.minY = nwPosition.y; + this.maxX = sePosition.x; + this.maxY = sePosition.y; + }, + + setDragHandle : function(handle) { + if (handle && handle != null) + this.handle = handle; + else + this.handle = this; + + this.handle.group = this; + this.onmousedown = null; + this.handle.onmousedown = Drag.onMouseDown; + }, + + setDragThreshold : function(threshold) { + if (isNaN(parseInt(threshold))) return; + + this.threshold = threshold; + }, + + setDragThresholdX : function(threshold) { + if (isNaN(parseInt(threshold))) return; + + this.thresholdX = threshold; + }, + + setDragThresholdY : function(threshold) { + if (isNaN(parseInt(threshold))) return; + + this.thresholdY = threshold; + }, + + onMouseDown : function(event) { + event = Drag.fixEvent(event); + Drag.group = this.group; + + var group = this.group; + var mouse = event.windowCoordinate; + var nwOffset = Coordinates.northwestOffset(group, true); + var nwPosition = Coordinates.northwestPosition(group); + var sePosition = Coordinates.southeastPosition(group); + var seOffset = Coordinates.southeastOffset(group, true); + + group.originalOpacity = group.style.opacity; + group.originalZIndex = group.style.zIndex; + group.initialWindowCoordinate = mouse; + // TODO: need a better name, but don't yet understand how it + // participates in the magic while dragging + group.dragCoordinate = mouse; + + Drag.showStatus(mouse, nwPosition, sePosition, nwOffset, seOffset); + + group.onDragStart(nwPosition, sePosition, nwOffset, seOffset); + + // TODO: need better constraint API + if (group.minX != null) + group.minMouseX = mouse.x - nwPosition.x + + group.minX - nwOffset.x; + if (group.maxX != null) + group.maxMouseX = group.minMouseX + group.maxX - group.minX; + + if (group.minY != null) + group.minMouseY = mouse.y - nwPosition.y + + group.minY - nwOffset.y; + if (group.maxY != null) + group.maxMouseY = group.minMouseY + group.maxY - group.minY; + + group.mouseMin = new Coordinate(group.minMouseX, group.minMouseY); + group.mouseMax = new Coordinate(group.maxMouseX, group.maxMouseY); + + document.onmousemove = Drag.onMouseMove; + document.onmouseup = Drag.onMouseUp; + + return false; + }, + + showStatus : function(mouse, nwPosition, sePosition, nwOffset, seOffset) { + window.status = + "mouse: " + mouse.toString() + " " + + "NW pos: " + nwPosition.toString() + " " + + "SE pos: " + sePosition.toString() + " " + + "NW offset: " + nwOffset.toString() + " " + + "SE offset: " + seOffset.toString(); + }, + + onMouseMove : function(event) { + event = Drag.fixEvent(event); + var group = Drag.group; + var mouse = event.windowCoordinate; + var nwOffset = Coordinates.northwestOffset(group, true); + var nwPosition = Coordinates.northwestPosition(group); + var sePosition = Coordinates.southeastPosition(group); + var seOffset = Coordinates.southeastOffset(group, true); + + Drag.showStatus(mouse, nwPosition, sePosition, nwOffset, seOffset); + + if (!Drag.isDragging) { + if (group.threshold > 0) { + var distance = group.initialWindowCoordinate.distance( + mouse); + if (distance < group.threshold) return true; + } else if (group.thresholdY > 0) { + var deltaY = Math.abs(group.initialWindowCoordinate.y - mouse.y); + if (deltaY < group.thresholdY) return true; + } else if (group.thresholdX > 0) { + var deltaX = Math.abs(group.initialWindowCoordinate.x - mouse.x); + if (deltaX < group.thresholdX) return true; + } + + Drag.isDragging = true; + group.style["zIndex"] = Drag.BIG_Z_INDEX; + group.style["opacity"] = 0.75; + } + + // TODO: need better constraint API + var adjusted = mouse.constrain(group.mouseMin, group.mouseMax); + nwPosition = nwPosition.plus(adjusted.minus(group.dragCoordinate)); + nwPosition.reposition(group); + group.dragCoordinate = adjusted; + + // once dragging has started, the position of the group + // relative to the mouse should stay fixed. They can get out + // of sync if the DOM is manipulated while dragging, so we + // correct the error here + // + // TODO: what we really want to do is find the offset from + // our corner to the mouse coordinate and adjust to keep it + // the same + var offsetBefore = Coordinates.northwestOffset(group); + group.onDrag(nwPosition, sePosition, nwOffset, seOffset); + var offsetAfter = Coordinates.northwestOffset(group); + + if (!offsetBefore.equals(offsetAfter)) { + var errorDelta = offsetBefore.minus(offsetAfter); + nwPosition = Coordinates.northwestPosition(group).plus(errorDelta); + nwPosition.reposition(group); + } + + return false; + }, + + onMouseUp : function(event) { + event = Drag.fixEvent(event); + var group = Drag.group; + + var mouse = event.windowCoordinate; + var nwOffset = Coordinates.northwestOffset(group, true); + var nwPosition = Coordinates.northwestPosition(group); + var sePosition = Coordinates.southeastPosition(group); + var seOffset = Coordinates.southeastOffset(group, true); + + document.onmousemove = null; + document.onmouseup = null; + group.onDragEnd(nwPosition, sePosition, nwOffset, seOffset); + + if (Drag.isDragging) { + // restoring zIndex before opacity avoids visual flicker in Firefox + group.style["zIndex"] = group.originalZIndex; + group.style["opacity"] = group.originalOpacity; + } + + Drag.group = null; + Drag.isDragging = false; + + return false; + }, + + fixEvent : function(event) { + if (typeof event == 'undefined') event = window.event; + Coordinates.fixEvent(event); + + return event; + } +}; diff --git a/vendor/assets/javascripts/dragsort.js b/vendor/assets/javascripts/dragsort.js new file mode 100644 index 0000000..6356663 --- /dev/null +++ b/vendor/assets/javascripts/dragsort.js @@ -0,0 +1,234 @@ +// TODO: refactor away duplicationg in DragSort and DragSortX + +var BetterDragSort = { + + makeListSortable : function(list) { + var items = list.getElementsByTagName("li"); + + for (var i = 0; i < items.length; i++) { + BetterDragSort.makeItemSortable(items[i]); + } + }, + + makeItemSortable : function(item) { + Drag.makeDraggable(item); + item.setDragThresholdY(5); + + item.onDragStart = BetterDragSort.onDragStart; + item.onDrag = BetterDragSort.onDrag; + item.onDragEnd = BetterDragSort.onDragEnd; + }, + + onDragStart : function(nwPosition, sePosition, nwOffset, seOffset) { + var items = this.parentNode.getElementsByTagName("li"); + var minOffset = Coordinates.northwestOffset(items[0], true); + var maxOffset = minOffset; + for (var i = 0; i < items.length; i++) { + maxOffset = maxOffset.max(Coordinates.northwestOffset(items[i], true)); + } + this.constrain(minOffset, maxOffset); + }, + + onDrag : function(nwPosition, sePosition, nwOffset, seOffset) { + var swapper = null; + + var next = DragUtils.nextItem(this); + while (next != null) { + var nextNWOffset = Coordinates.northwestOffset(next, true); + var nextSEOffset = Coordinates.southeastOffset(next, true); + + if (nwOffset.y >= (nextNWOffset.y - 2) && + nwOffset.y <= (nextSEOffset.y + 2) && + nwOffset.x >= (nextNWOffset.x - 2) && + nwOffset.x <= (nextSEOffset.x + 2)) { + var swapper = next; + break; + } + var next = DragUtils.nextItem(next); + } + if (swapper != null) { + BetterDragSort.moveAfter(this, swapper); + return; + } + + var previous = DragUtils.previousItem(this); + while (previous != null) { + var previousNWOffset = Coordinates.northwestOffset(previous, true); + var previousSEOffset = Coordinates.southeastOffset(previous, true); + + var fudgeFactor = 2; + if (nwOffset.y >= (previousNWOffset.y - fudgeFactor) && + nwOffset.y <= (previousSEOffset.y + fudgeFactor) && + nwOffset.x >= (previousNWOffset.x - fudgeFactor) && + nwOffset.x <= (previousSEOffset.x + fudgeFactor)) { + var swapper = previous; + break; + } + var previous = DragUtils.previousItem(previous); + } + if (swapper != null) { + BetterDragSort.moveBefore(this, swapper); + return; + } + }, + + moveAfter : function(item1, item2) { + var parent = item1.parentNode; + parent.removeChild(item1); + parent.insertBefore(item1, item2.nextSibling); + + item1.style["top"] = "0px"; + item1.style["left"] = "0px"; + }, + + moveBefore : function(item1, item2) { + var parent = item1.parentNode; + parent.removeChild(item1); + parent.insertBefore(item1, item2); + + item1.style["top"] = "0px"; + item1.style["left"] = "0px"; + }, + + onDragEnd : function(nwPosition, sePosition, nwOffset, seOffset) { + this.style["top"] = "0px"; + this.style["left"] = "0px"; + } +}; + + +var DragSort = { + + makeListSortable : function(list) { + var items = list.getElementsByTagName("li"); + + for (var i = 0; i < items.length; i++) { + DragSort.makeItemSortable(items[i]); + } + }, + + makeItemSortable : function(item) { + Drag.makeDraggable(item); + item.setDragThresholdY(5); + + item.onDragStart = DragSort.onDragStart; + item.onDrag = DragSort.onDrag; + item.onDragEnd = DragSort.onDragEnd; + }, + + onDragStart : function(nwPosition, sePosition, nwOffset, seOffset) { + var items = this.parentNode.getElementsByTagName("li"); + var minOffset = Coordinates.northwestOffset(items[0], true); + var maxOffset = Coordinates.northwestOffset(items[items.length - 1], true); + this.constrain(minOffset, maxOffset); + }, + + onDrag : function(nwPosition, sePosition, nwOffset, seOffset) { + var parent = this.parentNode; + + var item = this; + var next = DragUtils.nextItem(item); + while (next != null && this.offsetTop >= next.offsetTop - 2) { + var item = next; + var next = DragUtils.nextItem(item); + } + if (this != item) { + DragUtils.swap(this, next); + return; + } + + var item = this; + var previous = DragUtils.previousItem(item); + while (previous != null && this.offsetTop <= previous.offsetTop + 2) { + var item = previous; + var previous = DragUtils.previousItem(item); + } + if (this != item) { + DragUtils.swap(this, item); + return; + } + }, + + onDragEnd : function(nwPosition, sePosition, nwOffset, seOffset) { + this.style["top"] = "0px"; + this.style["left"] = "0px"; + } +}; + +var DragSortX = { + + makeListSortable : function(list) { + var items = list.getElementsByTagName("li"); + + var minOffset = Coordinates.northwestOffset(items[0], true); + var maxOffset = Coordinates.northwestOffset(items[items.length - 1], true); + + for (var i = 0; i < items.length; i++) { + Drag.makeDraggable(items[i]); + items[i].constrain(minOffset, maxOffset); + items[i].setDragThresholdX(5); + + items[i].onDrag = DragSortX.onDrag; + + items[i].onDragEnd = function(nwPosition, sePosition, nwOffset, seOffset) { + this.style["top"] = "0px"; + this.style["left"] = "0px"; + }; + } + }, + + onDrag : function(nwPosition, sePosition, nwOffset, seOffset) { + var parent = this.parentNode; + + var item = this; + var next = DragUtils.nextItem(item); + while (next != null && this.offsetLeft >= next.offsetLeft - 2) { + var item = next; + var next = DragUtils.nextItem(item); + } + if (this != item) { + DragUtils.swap(this, next); + return; + } + + var item = this; + var previous = DragUtils.previousItem(item); + while (previous != null && this.offsetLeft <= previous.offsetLeft + 2) { + var item = previous; + var previous = DragUtils.previousItem(item); + } + if (this != item) { + DragUtils.swap(this, item); + return; + } + } +}; + +var DragUtils = { + swap : function(item1, item2) { + var parent = item1.parentNode; + parent.removeChild(item1); + parent.insertBefore(item1, item2); + + item1.style["top"] = "0px"; + item1.style["left"] = "0px"; + }, + + nextItem : function(item) { + var sibling = item.nextSibling; + while (sibling != null) { + if (sibling.nodeName == item.nodeName) return sibling; + sibling = sibling.nextSibling; + } + return null; + }, + + previousItem : function(item) { + var sibling = item.previousSibling; + while (sibling != null) { + if (sibling.nodeName == item.nodeName) return sibling; + sibling = sibling.previousSibling; + } + return null; + } +}; |