Starting to get the project set up. Nothing is even nearly complete yet.
This commit is contained in:
		
						commit
						b6a24bc1ce
					
				
							
								
								
									
										28
									
								
								Capstanfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								Capstanfile
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,28 @@ | |||
| 
 | ||||
| # | ||||
| # Name of the base image. Capstan will download this automatically from | ||||
| # Cloudius S3 repository. | ||||
| # | ||||
| #base: cloudius/osv | ||||
| base: cloudius/osv-openjdk8 | ||||
| 
 | ||||
| # | ||||
| # The command line passed to OSv to start up the application. | ||||
| # | ||||
| cmdline: /java.so -jar /youyesyet/app.jar | ||||
| 
 | ||||
| # | ||||
| # The command to use to build the application. | ||||
| # You can use any build tool/command (make/rake/lein/boot) - this runs locally on your machine | ||||
| # | ||||
| # For Leiningen, you can use: | ||||
| #build: lein uberjar | ||||
| # For Boot, you can use: | ||||
| #build: boot build | ||||
| 
 | ||||
| # | ||||
| # List of files that are included in the generated image. | ||||
| # | ||||
| files: | ||||
|   /youyesyet/app.jar: ./target/uberjar/youyesyet.jar | ||||
| 
 | ||||
							
								
								
									
										8
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| FROM java:8-alpine | ||||
| MAINTAINER Your Name <you@example.com> | ||||
| 
 | ||||
| ADD target/uberjar/youyesyet.jar /youyesyet/app.jar | ||||
| 
 | ||||
| EXPOSE 3000 | ||||
| 
 | ||||
| CMD ["java", "-jar", "/youyesyet/app.jar"] | ||||
							
								
								
									
										258
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,258 @@ | |||
| # GNU GENERAL PUBLIC LICENSE | ||||
| 
 | ||||
| Version 2, June 1991 | ||||
| 
 | ||||
| Copyright (C) 1989, 1991 Free Software Foundation, Inc.   | ||||
| 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA | ||||
| 
 | ||||
| Everyone is permitted to copy and distribute verbatim copies | ||||
| of this license document, but changing it is not allowed. | ||||
| Preamble | ||||
| 
 | ||||
| The licenses for most software are designed to take away your freedom to share  | ||||
| and change it. By contrast, the GNU General Public License is intended to guarantee  | ||||
| your freedom to share and change free software--to make sure the software is free  | ||||
| for all its users. This General Public License applies to most of the Free  | ||||
| Software Foundation's software and to any other program whose authors commit to  | ||||
| using it. (Some other Free Software Foundation software is covered by the GNU  | ||||
| Lesser General Public License instead.) You can apply it to your programs, too. | ||||
| 
 | ||||
| When we speak of free software, we are referring to freedom, not price. Our  | ||||
| General Public Licenses are designed to make sure that you have the freedom to  | ||||
| distribute copies of free software (and charge for this service if you wish),  | ||||
| that you receive source code or can get it if you want it, that you can change  | ||||
| the software or use pieces of it in new free programs; and that you know you  | ||||
| can do these things. | ||||
| 
 | ||||
| To protect your rights, we need to make restrictions that forbid anyone to  | ||||
| deny you these rights or to ask you to surrender the rights. These restrictions  | ||||
| translate to certain responsibilities for you if you distribute copies of the  | ||||
| software, or if you modify it. | ||||
| 
 | ||||
| For example, if you distribute copies of such a program, whether gratis or for  | ||||
| a fee, you must give the recipients all the rights that you have. You must make  | ||||
| sure that they, too, receive or can get the source code. And you must show them  | ||||
| these terms so they know their rights. | ||||
| 
 | ||||
| We protect your rights with two steps: (1) copyright the software, and (2)  | ||||
| offer you this license which gives you legal permission to copy, distribute  | ||||
| and/or modify the software. | ||||
| 
 | ||||
| Also, for each author's protection and ours, we want to make certain that  | ||||
| everyone understands that there is no warranty for this free software. If the  | ||||
| software is modified by someone else and passed on, we want its recipients to  | ||||
| know that what they have is not the original, so that any problems introduced  | ||||
| by others will not reflect on the original authors' reputations. | ||||
| 
 | ||||
| Finally, any free program is threatened constantly by software patents. We wish  | ||||
| to avoid the danger that redistributors of a free program will individually  | ||||
| obtain patent licenses, in effect making the program proprietary. To prevent  | ||||
| this, we have made it clear that any patent must be licensed for everyone's  | ||||
| free use or not licensed at all. | ||||
| 
 | ||||
| The precise terms and conditions for copying, distribution and modification follow. | ||||
| 
 | ||||
| ## TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | ||||
| 
 | ||||
| 0. 	This License applies to any program or other work which contains a notice  | ||||
| 	placed by the copyright holder saying it may be distributed under the terms of  | ||||
| 	this General Public License. The "Program", below, refers to any such program  | ||||
| 	or work, and a "work based on the Program" means either the Program or any  | ||||
| 	derivative work under copyright law: that is to say, a work containing the  | ||||
| 	Program or a portion of it, either verbatim or with modifications and/or  | ||||
| 	translated into another language. (Hereinafter, translation is included without  | ||||
| 	limitation in the term "modification".) Each licensee is addressed as "you". | ||||
| 	 | ||||
| 	Activities other than copying, distribution and modification are not covered by  | ||||
| 	this License; they are outside its scope. The act of running the Program is not  | ||||
| 	restricted, and the output from the Program is covered only if its contents  | ||||
| 	constitute a work based on the Program (independent of having been made by  | ||||
| 	running the Program). Whether that is true depends on what the Program does. | ||||
| 
 | ||||
| 1. 	You may copy and distribute 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 and disclaimer of warranty;  | ||||
| 	keep intact all the notices that refer to this License and to the absence of any  | ||||
| 	warranty; and give any other recipients of the Program a copy of this License  | ||||
| 	along with the Program. | ||||
| 	 | ||||
| 	You may charge a fee for the physical act of transferring a copy, and you may at  | ||||
| 	your option offer warranty protection in exchange for a fee. | ||||
| 	 | ||||
| 2. 	You may modify your copy or copies of the Program or any portion of it, thus  | ||||
| 	forming a work based on the Program, and copy and distribute such modifications  | ||||
| 	or work under the terms of Section 1 above, provided that you also meet all of  | ||||
| 	these conditions: | ||||
| 	 | ||||
| 	a) You must cause the modified files to carry prominent notices stating that  | ||||
| 	   you changed the files and the date of any change. | ||||
| 	b) You must cause any work that you distribute or publish, that in whole or in  | ||||
| 	   part contains or is derived from the Program or any part thereof, to be  | ||||
| 	   licensed as a whole at no charge to all third parties under the terms of this  | ||||
| 	   License. | ||||
| 	c) If the modified program normally reads commands interactively when run, you | ||||
| 	   must cause it, when started running for such interactive use in the most  | ||||
| 	   ordinary way, to print or display an announcement including an appropriate  | ||||
| 	   copyright notice and a notice that there is no warranty (or else, saying  | ||||
| 	   that you provide a warranty) and that users may redistribute the program  | ||||
| 	   under these conditions, and telling the user how to view a copy of this  | ||||
| 	   License. (Exception: if the Program itself is interactive but does not  | ||||
| 	   normally print such an announcement, your work based on the Program is not  | ||||
| 	   required to print an announcement.) | ||||
| 	    | ||||
| 	These requirements apply to the modified work as a whole. If identifiable  | ||||
| 	sections of that work are not derived from the Program, and can be reasonably  | ||||
| 	considered independent and separate works in themselves, then this License,  | ||||
| 	and its terms, do not apply to those sections when you distribute them as  | ||||
| 	separate works. But when you distribute the same sections as part of a whole  | ||||
| 	which is a work based on the Program, the distribution of the whole must be on  | ||||
| 	the terms of this License, whose permissions for other licensees extend to the  | ||||
| 	entire whole, and thus to each and every part regardless of who wrote it. | ||||
| 	 | ||||
| 	Thus, it is not the intent of this section to claim rights or contest your  | ||||
| 	rights to work written entirely by you; rather, the intent is to exercise the  | ||||
| 	right to control the distribution of derivative or collective works based on  | ||||
| 	the Program. | ||||
| 	 | ||||
| 	In addition, mere aggregation of another work not based on the Program with the  | ||||
| 	Program (or with a work based on the Program) on a volume of a storage or  | ||||
| 	distribution medium does not bring the other work under the scope of this  | ||||
| 	License. | ||||
| 	 | ||||
| 	3. You may copy and distribute the Program (or a work based on it, under  | ||||
| 	Section 2) in object code or executable form under the terms of Sections 1  | ||||
| 	and 2 above provided that you also do one of the following: | ||||
| 	 | ||||
| 	a) Accompany it with the complete corresponding machine-readable source code,  | ||||
| 	   which must be distributed under the terms of Sections 1 and 2 above on a  | ||||
| 	   medium customarily used for software interchange; or, | ||||
| 	b) Accompany it with a written offer, valid for at least three years, to give | ||||
| 	   any third party, for a charge no more than your cost of physically  | ||||
| 	   performing source distribution, a complete machine-readable copy of the  | ||||
| 	   corresponding source code, to be distributed under the terms of Sections 1  | ||||
| 	   and 2 above on a medium customarily used for software interchange; or, | ||||
| 	c) Accompany it with the information you received as to the offer to distribute | ||||
| 	   corresponding source code. (This alternative is allowed only for  | ||||
| 	   noncommercial distribution and only if you received the program in object  | ||||
| 	   code or executable form with such an offer, in accord with Subsection b  | ||||
| 	   above.) | ||||
|     | ||||
| 	The source code for a work means the preferred form of the work for making  | ||||
| 	modifications to it. For an executable work, complete source code means all the  | ||||
| 	source code for all modules it contains, plus any associated interface  | ||||
| 	definition files, plus the scripts used to control compilation and installation  | ||||
| 	of the executable. However, as a special exception, the source code distributed  | ||||
| 	need not include anything that is normally distributed (in either source or  | ||||
| 	binary form) with the major components (compiler, kernel, and so on) of the  | ||||
| 	operating system on which the executable runs, unless that component itself  | ||||
| 	accompanies the executable. | ||||
| 	 | ||||
| 	If distribution of executable or object code is made by offering access to  | ||||
| 	copy from a designated place, then offering equivalent access to copy the  | ||||
| 	source code from the same place counts as distribution of the source code,  | ||||
| 	even though third parties are not compelled to copy the source along with the  | ||||
| 	object code. | ||||
| 
 | ||||
| 4. 	You may not copy, modify, sublicense, or distribute the Program except as  | ||||
| 	expressly provided under this License. Any attempt otherwise to copy, modify,  | ||||
| 	sublicense or distribute the Program is void, and will automatically  | ||||
| 	terminate your rights under this License. However, parties who have  | ||||
| 	received copies, or rights, from you under this License will not have their  | ||||
| 	licenses terminated so long as such parties remain in full compliance. | ||||
| 
 | ||||
| 5. 	You are not required to accept this License, since you have not signed it.  | ||||
| 	However, nothing else grants you permission to modify or distribute the  | ||||
| 	Program or its derivative works. These actions are prohibited by law if  | ||||
| 	you do not accept this License. Therefore, by modifying or distributing  | ||||
| 	the Program (or any work based on the Program), you indicate your  | ||||
| 	acceptance of this License to do so, and all its terms and conditions  | ||||
| 	for copying, distributing or modifying the Program or works based on it. | ||||
| 
 | ||||
| 6. 	Each time you redistribute the Program (or any work based on the Program),  | ||||
| 	the recipient automatically receives a license from the original licensor | ||||
| 	to copy, distribute or modify the Program subject to these terms and  | ||||
| 	conditions. You may not impose any further restrictions on the recipients'  | ||||
| 	exercise of the rights granted herein. You are not responsible for enforcing  | ||||
| 	compliance by third parties to this License. | ||||
| 
 | ||||
| 7.  If, as a consequence of a court judgment or allegation of patent infringement  | ||||
| 	or for any other reason (not limited to patent issues), 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 distribute so as to satisfy  | ||||
| 	simultaneously your obligations under this License and any other pertinent  | ||||
| 	obligations, then as a consequence you may not distribute the Program at  | ||||
| 	all. For example, if a patent license would not permit royalty-free  | ||||
| 	redistribution of the Program by all those who receive copies directly or  | ||||
| 	indirectly through you, then the only way you could satisfy both it and  | ||||
| 	this License would be to refrain entirely from distribution of the Program. | ||||
| 
 | ||||
| 	If any portion of this section is held invalid or unenforceable under any  | ||||
| 	particular circumstance, the balance of the section is intended to apply  | ||||
| 	and the section as a whole is intended to apply in other circumstances. | ||||
| 
 | ||||
| 	It is not the purpose of this section to induce you to infringe any patents  | ||||
| 	or other property right claims or to contest validity of any such claims;  | ||||
| 	this section has the sole purpose of protecting the integrity of the free  | ||||
| 	software distribution system, which is implemented by public license  | ||||
| 	practices. Many people have made generous contributions to the wide range  | ||||
| 	of software distributed through that system in reliance on consistent  | ||||
| 	application of that system; it is up to the author/donor to decide if he or  | ||||
| 	she is willing to distribute software through any other system and a  | ||||
| 	licensee cannot impose that choice. | ||||
| 	 | ||||
| 	This section is intended to make thoroughly clear what is believed to be a  | ||||
| 	consequence of the rest of this License. | ||||
| 	 | ||||
| 8. 	If the distribution and/or use of the Program is restricted in certain  | ||||
| 	countries either by patents or by copyrighted interfaces, the original  | ||||
| 	copyright holder who places the Program under this License may add an  | ||||
| 	explicit geographical distribution limitation excluding those countries,  | ||||
| 	so that distribution is permitted only in or among countries not thus  | ||||
| 	excluded. In such case, this License incorporates the limitation as if  | ||||
| 	written in the body of this License. | ||||
| 
 | ||||
| 9. 	The Free Software Foundation may publish revised and/or new versions of the  | ||||
| 	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 a version number of this License which applies to it and  | ||||
| 	"any later version", you have the option of following the terms and  | ||||
| 	conditions either of that version or of any later version published  | ||||
| 	by the Free Software Foundation. If the Program does not specify a version  | ||||
| 	number of this License, you may choose any version ever published by the  | ||||
| 	Free Software Foundation. | ||||
| 
 | ||||
| 10. If you wish to incorporate parts of the Program into other free programs  | ||||
| 	whose distribution conditions are different, write to the author to ask for  | ||||
| 	permission. For software which is copyrighted by the Free Software  | ||||
| 	Foundation, write to the Free Software Foundation; we sometimes make  | ||||
| 	exceptions for this. Our decision will be guided by the two goals of  | ||||
| 	preserving the free status of all derivatives of our free software and of  | ||||
| 	promoting the sharing and reuse of software generally. | ||||
| 
 | ||||
| NO WARRANTY | ||||
| 
 | ||||
| 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. | ||||
| 
 | ||||
| 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL  | ||||
| 	ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE | ||||
| 	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. | ||||
| 
 | ||||
| END OF TERMS AND CONDITIONS | ||||
							
								
								
									
										1
									
								
								Procfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								Procfile
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| web: java $JVM_OPTS -cp target/uberjar/youyesyet.jar clojure.main -m youyesyet.core | ||||
							
								
								
									
										28
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,28 @@ | |||
| # youyesyet | ||||
| 
 | ||||
| A web-app intended to be used by canvassers campaigning for a 'Yes' vote in the second independence referendum. | ||||
| 
 | ||||
| The web-app will be delivered to canvassers out knocking doors primarily through an HTML5/React single-page app; it's possible that someone else may do an Android of iPhone native app to address the same back end but at present I have no plans for this. | ||||
| 
 | ||||
| There must also be an administrative interface through which privileged users can set the system up and authorise canvassers, and a 'followup' interface through which issue-expert specialist canvassers can address particular electors' queries. | ||||
| 
 | ||||
| generated using Luminus version "2.9.11.05" | ||||
| 
 | ||||
| ## Prerequisites | ||||
| 
 | ||||
| You will need [Leiningen][1] 2.0 or above installed. The database required must be [Postgres][2]. | ||||
| 
 | ||||
| [1]: https://github.com/technomancy/leiningen | ||||
| [2]: https://www.postgresql.org/ | ||||
| 
 | ||||
| ## Running | ||||
| 
 | ||||
| To start a web server for the application, run: | ||||
| 
 | ||||
|     lein run | ||||
| 
 | ||||
| ## License | ||||
| 
 | ||||
| Copyright © 2016 Simon Brooke for the Radical Independence Campaign. | ||||
| 
 | ||||
| Licensed under the GNU General Public License, version 2.0 or (at your option) any later version. | ||||
							
								
								
									
										
											BIN
										
									
								
								dummies/mapview.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								dummies/mapview.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 3.8 MiB | 
							
								
								
									
										4874
									
								
								dummies/mapview.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4874
									
								
								dummies/mapview.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| After Width: | Height: | Size: 350 KiB | 
							
								
								
									
										
											BIN
										
									
								
								dummies/mapview.xcf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								dummies/mapview.xcf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								dummies/mapview_800.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								dummies/mapview_800.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 665 KiB | 
							
								
								
									
										
											BIN
										
									
								
								dummies/mapview_800.xcf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								dummies/mapview_800.xcf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								dummies/occupants.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								dummies/occupants.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 359 KiB | 
							
								
								
									
										454
									
								
								dummies/occupants.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										454
									
								
								dummies/occupants.svg
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,454 @@ | |||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||||
| 
 | ||||
| <svg | ||||
|    xmlns:dc="http://purl.org/dc/elements/1.1/" | ||||
|    xmlns:cc="http://creativecommons.org/ns#" | ||||
|    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | ||||
|    xmlns:svg="http://www.w3.org/2000/svg" | ||||
|    xmlns="http://www.w3.org/2000/svg" | ||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||
|    width="210mm" | ||||
|    height="297mm" | ||||
|    viewBox="0 0 744.09448819 1052.3622047" | ||||
|    id="svg4435" | ||||
|    version="1.1" | ||||
|    inkscape:version="0.91 r13725" | ||||
|    sodipodi:docname="occupants.svg" | ||||
|    inkscape:export-filename="/home/simon/workspace/youyesyet/dummies/occupants.png" | ||||
|    inkscape:export-xdpi="300" | ||||
|    inkscape:export-ydpi="300"> | ||||
|   <defs | ||||
|      id="defs4437" /> | ||||
|   <sodipodi:namedview | ||||
|      id="base" | ||||
|      pagecolor="#ffffff" | ||||
|      bordercolor="#666666" | ||||
|      borderopacity="1.0" | ||||
|      inkscape:pageopacity="0.0" | ||||
|      inkscape:pageshadow="2" | ||||
|      inkscape:zoom="0.7" | ||||
|      inkscape:cx="51.779153" | ||||
|      inkscape:cy="490.59499" | ||||
|      inkscape:document-units="px" | ||||
|      inkscape:current-layer="layer1" | ||||
|      showgrid="true" | ||||
|      inkscape:window-width="1920" | ||||
|      inkscape:window-height="996" | ||||
|      inkscape:window-x="0" | ||||
|      inkscape:window-y="28" | ||||
|      inkscape:window-maximized="1"> | ||||
|     <inkscape:grid | ||||
|        type="xygrid" | ||||
|        id="grid4987" /> | ||||
|   </sodipodi:namedview> | ||||
|   <metadata | ||||
|      id="metadata4440"> | ||||
|     <rdf:RDF> | ||||
|       <cc:Work | ||||
|          rdf:about=""> | ||||
|         <dc:format>image/svg+xml</dc:format> | ||||
|         <dc:type | ||||
|            rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | ||||
|         <dc:title></dc:title> | ||||
|       </cc:Work> | ||||
|     </rdf:RDF> | ||||
|   </metadata> | ||||
|   <g | ||||
|      inkscape:label="Layer 1" | ||||
|      inkscape:groupmode="layer" | ||||
|      id="layer1"> | ||||
|     <rect | ||||
|        style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.4000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | ||||
|        id="rect5308" | ||||
|        width="700" | ||||
|        height="1020" | ||||
|        x="20" | ||||
|        y="12.362205" /> | ||||
|     <text | ||||
|        xml:space="preserve" | ||||
|        style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:45px;line-height:125%;font-family:'Arial Black';-inkscape-font-specification:'Arial Black, ';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|        x="81.428566" | ||||
|        y="85.219345" | ||||
|        id="text4983" | ||||
|        sodipodi:linespacing="125%"><tspan | ||||
|          sodipodi:role="line" | ||||
|          id="tspan4985" | ||||
|          x="81.428566" | ||||
|          y="85.219345">43 Imaginary Terrace</tspan></text> | ||||
|     <path | ||||
|        style="fill:#808080;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|        d="m 151.42857,208.07649 c 0,0 -22.38786,-25.28537 -20,-40 3.02043,-18.6127 21.14382,-40 40,-40 18.85618,0 36.97957,21.3873 40,40 2.38786,14.71463 -20,40 -20,40 0,0 12.56519,-3.0796 20,0 7.43481,3.0796 9.36405,0.69304 20,20 10.63595,19.30696 22.85714,101.42857 22.85714,101.42857 l -20,0 -22.85714,-81.42857 0,78.57143 20,161.42857 -40,0 -20,-120 -20,120 -40,0 17.14286,-158.57143 2.85714,-81.42857 -17.14286,80 -19.999998,0 c 0,0 7.203708,-80.79574 17.142858,-100 9.93915,-19.20426 12.56519,-16.9204 20,-20 7.43481,-3.0796 20,0 20,0" | ||||
|        id="path4989" | ||||
|        inkscape:connector-curvature="0" | ||||
|        sodipodi:nodetypes="csssczzccccccccccccczzc" /> | ||||
|     <path | ||||
|        sodipodi:nodetypes="csssczzccccccccccccccccczzc" | ||||
|        inkscape:connector-curvature="0" | ||||
|        id="path4991" | ||||
|        d="m 351.42857,208.07649 c 0,0 -22.38786,-25.28537 -20,-40 3.02043,-18.6127 21.14382,-40 40,-40 18.85618,0 36.97957,21.3873 40,40 2.38786,14.71463 -20,40 -20,40 0,0 12.56519,-3.0796 20,0 7.43481,3.0796 9.36405,0.69304 20,20 10.63595,19.30696 22.85714,101.42857 22.85714,101.42857 l -20,0 -22.85714,-81.42857 0,78.57143 C 408.57143,353.55268 430,380.45744 427.14286,407.3622 l -24.28572,-2.5 -8.57143,83.21429 c -13.50649,0 -41.87328,0 -22.85714,0 l 0,-77.14286 -1.42857,78.57143 -21.42857,-2.85714 -10.71429,-76.42857 -23.57143,-2.14286 c -3.33333,-26.19048 17.61905,-52.38095 14.28572,-78.57143 l 2.85714,-81.42857 -17.14286,80 -20,0 c 0,0 7.20371,-80.79574 17.14286,-100 9.93915,-19.20426 12.56519,-16.9204 20,-20 7.43481,-3.0796 20,0 20,0" | ||||
|        style="fill:#808080;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> | ||||
|     <path | ||||
|        style="fill:#808080;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|        d="m 548.57143,208.07649 c 0,0 -22.38786,-25.28537 -20,-40 3.02043,-18.6127 21.14382,-40 40,-40 18.85618,0 36.97957,21.3873 40,40 2.38786,14.71463 -20,40 -20,40 0,0 12.56519,-3.0796 20,0 7.43481,3.0796 9.36405,0.69304 20,20 10.63595,19.30696 22.85714,101.42857 22.85714,101.42857 l -20,0 -22.85714,-81.42857 0,78.57143 c -2.85714,26.90476 18.57143,53.80952 15.71429,80.71428 l -24.28572,-2.5 -8.57143,83.21429 c -13.50649,0 -41.87328,0 -22.85714,0 l 0,-77.14286 -1.42857,78.57143 -21.42857,-2.85714 L 535,410.21935 511.42857,408.07649 c -3.33333,-26.19048 17.61905,-52.38095 14.28572,-78.57143 l 2.85714,-81.42857 -17.14286,80 -20,0 c 0,0 7.20371,-80.79574 17.14286,-100 9.93915,-19.20426 12.56519,-16.9204 20,-20 7.43481,-3.0796 20,0 20,0" | ||||
|        id="path4993" | ||||
|        inkscape:connector-curvature="0" | ||||
|        sodipodi:nodetypes="csssczzccccccccccccccccczzc" /> | ||||
|     <text | ||||
|        xml:space="preserve" | ||||
|        style="font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:20px;line-height:125%;font-family:'URW Chancery L';-inkscape-font-specification:'URW Chancery L Bold Italic';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|        x="110" | ||||
|        y="549.50507" | ||||
|        id="text4995" | ||||
|        sodipodi:linespacing="125%"><tspan | ||||
|          sodipodi:role="line" | ||||
|          id="tspan4997" | ||||
|          x="110" | ||||
|          y="549.50507" | ||||
|          style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Arial;-inkscape-font-specification:Arial">John McNeil</tspan></text> | ||||
|     <text | ||||
|        xml:space="preserve" | ||||
|        style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:20px;line-height:125%;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|        x="320" | ||||
|        y="549.50507" | ||||
|        id="text4999" | ||||
|        sodipodi:linespacing="125%"><tspan | ||||
|          sodipodi:role="line" | ||||
|          id="tspan5001" | ||||
|          x="320" | ||||
|          y="549.50507">Anne McNeil</tspan></text> | ||||
|     <text | ||||
|        xml:space="preserve" | ||||
|        style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:20px;line-height:125%;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|        x="530" | ||||
|        y="549.50507" | ||||
|        id="text5003" | ||||
|        sodipodi:linespacing="125%"><tspan | ||||
|          sodipodi:role="line" | ||||
|          id="tspan5005" | ||||
|          x="530" | ||||
|          y="549.50507">Helen McNeil</tspan></text> | ||||
|     <g | ||||
|        id="g5166" | ||||
|        transform="matrix(2,0,0,2,1117.4431,-216.22311)"> | ||||
|       <path | ||||
|          sodipodi:nodetypes="ccccccccc" | ||||
|          inkscape:connector-curvature="0" | ||||
|          id="path5079" | ||||
|          d="m -300,452.3622 0,-40 c 0,-5 5,-10 10,-10 l 40,0 c 5,0 10,5 10,10 l 0,40 c 0,5 -5,10 -10,10 l -40,0 c -5,0 -10,-5 -10,-10 z" | ||||
|          style="fill:#808080;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> | ||||
|       <path | ||||
|          inkscape:connector-curvature="0" | ||||
|          id="path5088" | ||||
|          d="m -300,412.27292 0,8.92857 60,31.25 0,-8.92857 -60,-31.25" | ||||
|          style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> | ||||
|       <path | ||||
|          style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|          d="m -300,452.45149 0,-8.92857 60,-31.25 0,8.92857 -60,31.25" | ||||
|          id="path5090" | ||||
|          inkscape:connector-curvature="0" /> | ||||
|     </g> | ||||
|     <g | ||||
|        id="g5154" | ||||
|        transform="matrix(2,0,0,2,1117.4431,-216.22311)"> | ||||
|       <path | ||||
|          style="fill:#808080;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|          d="m -300,523.07649 0,-40 c 0,-5 5,-10 10,-10 l 40,0 c 5,0 10,5 10,10 l 0,40 c 0,5 -5,10 -10,10 l -40,0 c -5,0 -10,-5 -10,-10 z" | ||||
|          id="path5081" | ||||
|          inkscape:connector-curvature="0" | ||||
|          sodipodi:nodetypes="ccccccccc" /> | ||||
|       <path | ||||
|          style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|          d="m -300.16446,483.03742 0,8.92857 60,31.25 0,-8.92857 -60,-31.25" | ||||
|          id="path5117" | ||||
|          inkscape:connector-curvature="0" /> | ||||
|       <path | ||||
|          inkscape:connector-curvature="0" | ||||
|          id="path5119" | ||||
|          d="m -300.16446,523.21599 0,-8.92857 60,-31.25 0,8.92857 -60,31.25" | ||||
|          style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> | ||||
|       <rect | ||||
|          y="483.03741" | ||||
|          x="-275.34302" | ||||
|          height="25" | ||||
|          width="10" | ||||
|          id="rect5121" | ||||
|          style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.4000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | ||||
|       <rect | ||||
|          y="482.91116" | ||||
|          x="-277.31256" | ||||
|          height="40" | ||||
|          width="13.686545" | ||||
|          id="rect5125" | ||||
|          style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.4000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | ||||
|       <rect | ||||
|          y="498.03741" | ||||
|          x="-300.34302" | ||||
|          height="10" | ||||
|          width="60" | ||||
|          id="rect5127" | ||||
|          style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.4000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | ||||
|       <rect | ||||
|          y="483.03741" | ||||
|          x="-275.34302" | ||||
|          height="40" | ||||
|          width="10" | ||||
|          id="rect5129" | ||||
|          style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:2.4000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | ||||
|       <rect | ||||
|          y="499.55264" | ||||
|          x="-300.34302" | ||||
|          height="7.2220807" | ||||
|          width="60" | ||||
|          id="rect5131" | ||||
|          style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:2.4000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | ||||
|       <path | ||||
|          inkscape:connector-curvature="0" | ||||
|          id="path5133" | ||||
|          d="m -300.12977,484.76182 59.9778,31.18846 0,5.42957 -60.23034,-31.31472 z" | ||||
|          style="fill:#b3b3b3;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> | ||||
|       <path | ||||
|          style="fill:#b3b3b3;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|          d="m -240.40451,484.76182 -59.9778,31.18846 0,5.42957 60.23034,-31.31472 z" | ||||
|          id="path5135" | ||||
|          inkscape:connector-curvature="0" /> | ||||
|     </g> | ||||
|     <g | ||||
|        id="g5173" | ||||
|        transform="matrix(2,0,0,2,1117.4431,-216.22311)"> | ||||
|       <path | ||||
|          sodipodi:nodetypes="ccccccccc" | ||||
|          inkscape:connector-curvature="0" | ||||
|          id="path5083" | ||||
|          d="m -300,593.25506 0,-40 c 0,-5 5,-10 10,-10 l 40,0 c 5,0 10,5 10,10 l 0,40 c 0,5 -5,10 -10,10 l -40,0 c -5,0 -10,-5 -10,-10 z" | ||||
|          style="fill:#808080;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> | ||||
|       <text | ||||
|          sodipodi:linespacing="125%" | ||||
|          id="text4292" | ||||
|          y="594.78485" | ||||
|          x="-289.54495" | ||||
|          style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:20px;line-height:125%;font-family:'Arial Black';-inkscape-font-specification:'Arial Black, ';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|          xml:space="preserve"><tspan | ||||
|            style="font-size:60px;fill:#ffffff" | ||||
|            y="594.78485" | ||||
|            x="-289.54495" | ||||
|            id="tspan4294" | ||||
|            sodipodi:role="line">?</tspan></text> | ||||
|     </g> | ||||
|     <g | ||||
|        transform="matrix(2,0,0,2,913.15739,-216.22311)" | ||||
|        id="g5178"> | ||||
|       <path | ||||
|          style="fill:#808080;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|          d="m -300,452.3622 0,-40 c 0,-5 5,-10 10,-10 l 40,0 c 5,0 10,5 10,10 l 0,40 c 0,5 -5,10 -10,10 l -40,0 c -5,0 -10,-5 -10,-10 z" | ||||
|          id="path5180" | ||||
|          inkscape:connector-curvature="0" | ||||
|          sodipodi:nodetypes="ccccccccc" /> | ||||
|       <path | ||||
|          style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|          d="m -300,412.27292 0,8.92857 60,31.25 0,-8.92857 -60,-31.25" | ||||
|          id="path5182" | ||||
|          inkscape:connector-curvature="0" /> | ||||
|       <path | ||||
|          inkscape:connector-curvature="0" | ||||
|          id="path5184" | ||||
|          d="m -300,452.45149 0,-8.92857 60,-31.25 0,8.92857 -60,31.25" | ||||
|          style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> | ||||
|     </g> | ||||
|     <path | ||||
|        style="fill:#000080;fill-rule:evenodd;stroke:#000000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|        d="m 313.15739,829.92987 0,-80 c 0,-10 10,-20 20,-20 l 80,0 c 10,0 20,10 20,20 l 0,80 c 0,10 -10,20 -20,20 l -80,0 c -10,0 -20,-10 -20,-20 z" | ||||
|        id="path5188" | ||||
|        inkscape:connector-curvature="0" | ||||
|        sodipodi:nodetypes="ccccccccc" /> | ||||
|     <g | ||||
|        transform="matrix(2,0,0,2,913.15739,-216.22311)" | ||||
|        id="g5208"> | ||||
|       <path | ||||
|          style="fill:#808080;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|          d="m -300,593.25506 0,-40 c 0,-5 5,-10 10,-10 l 40,0 c 5,0 10,5 10,10 l 0,40 c 0,5 -5,10 -10,10 l -40,0 c -5,0 -10,-5 -10,-10 z" | ||||
|          id="path5210" | ||||
|          inkscape:connector-curvature="0" | ||||
|          sodipodi:nodetypes="ccccccccc" /> | ||||
|       <text | ||||
|          xml:space="preserve" | ||||
|          style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:20px;line-height:125%;font-family:'Arial Black';-inkscape-font-specification:'Arial Black, ';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|          x="-289.54495" | ||||
|          y="594.78485" | ||||
|          id="text5212" | ||||
|          sodipodi:linespacing="125%"><tspan | ||||
|            sodipodi:role="line" | ||||
|            id="tspan5214" | ||||
|            x="-289.54495" | ||||
|            y="594.78485" | ||||
|            style="font-size:60px;fill:#ffffff">?</tspan></text> | ||||
|     </g> | ||||
|     <path | ||||
|        style="fill:#000080;fill-rule:evenodd;stroke:#000000;stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|        d="m 104.58596,688.50129 0,-80 c 0,-10 10,-20 20,-20 l 80,0 c 10,0 20,10 20,20 l 0,80 c 0,10 -10,20 -20,20 l -80,0 c -10,0 -20,-10 -20,-20 z" | ||||
|        id="path5218" | ||||
|        inkscape:connector-curvature="0" | ||||
|        sodipodi:nodetypes="ccccccccc" /> | ||||
|     <path | ||||
|        style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|        d="m 104.58596,608.32273 0,17.85714 120,62.5 0,-17.85714 -120,-62.5" | ||||
|        id="path5220" | ||||
|        inkscape:connector-curvature="0" /> | ||||
|     <path | ||||
|        inkscape:connector-curvature="0" | ||||
|        id="path5222" | ||||
|        d="m 104.58596,688.67987 0,-17.85714 120,-62.5 0,17.85714 -120,62.5" | ||||
|        style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> | ||||
|     <g | ||||
|        id="g5224" | ||||
|        transform="matrix(2,0,0,2,704.58596,-216.22311)"> | ||||
|       <path | ||||
|          style="fill:#808080;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|          d="m -300,523.07649 0,-40 c 0,-5 5,-10 10,-10 l 40,0 c 5,0 10,5 10,10 l 0,40 c 0,5 -5,10 -10,10 l -40,0 c -5,0 -10,-5 -10,-10 z" | ||||
|          id="path5226" | ||||
|          inkscape:connector-curvature="0" | ||||
|          sodipodi:nodetypes="ccccccccc" /> | ||||
|       <path | ||||
|          style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|          d="m -300.16446,483.03742 0,8.92857 60,31.25 0,-8.92857 -60,-31.25" | ||||
|          id="path5228" | ||||
|          inkscape:connector-curvature="0" /> | ||||
|       <path | ||||
|          inkscape:connector-curvature="0" | ||||
|          id="path5230" | ||||
|          d="m -300.16446,523.21599 0,-8.92857 60,-31.25 0,8.92857 -60,31.25" | ||||
|          style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> | ||||
|       <rect | ||||
|          y="483.03741" | ||||
|          x="-275.34302" | ||||
|          height="25" | ||||
|          width="10" | ||||
|          id="rect5232" | ||||
|          style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.4000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | ||||
|       <rect | ||||
|          y="482.91116" | ||||
|          x="-277.31256" | ||||
|          height="40" | ||||
|          width="13.686545" | ||||
|          id="rect5234" | ||||
|          style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.4000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | ||||
|       <rect | ||||
|          y="498.03741" | ||||
|          x="-300.34302" | ||||
|          height="10" | ||||
|          width="60" | ||||
|          id="rect5236" | ||||
|          style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.4000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | ||||
|       <rect | ||||
|          y="483.03741" | ||||
|          x="-275.34302" | ||||
|          height="40" | ||||
|          width="10" | ||||
|          id="rect5238" | ||||
|          style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:2.4000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | ||||
|       <rect | ||||
|          y="499.55264" | ||||
|          x="-300.34302" | ||||
|          height="7.2220807" | ||||
|          width="60" | ||||
|          id="rect5240" | ||||
|          style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:none;stroke-width:2.4000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | ||||
|       <path | ||||
|          inkscape:connector-curvature="0" | ||||
|          id="path5242" | ||||
|          d="m -300.12977,484.76182 59.9778,31.18846 0,5.42957 -60.23034,-31.31472 z" | ||||
|          style="fill:#b3b3b3;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> | ||||
|       <path | ||||
|          style="fill:#b3b3b3;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|          d="m -240.40451,484.76182 -59.9778,31.18846 0,5.42957 60.23034,-31.31472 z" | ||||
|          id="path5244" | ||||
|          inkscape:connector-curvature="0" /> | ||||
|     </g> | ||||
|     <g | ||||
|        id="g5246" | ||||
|        transform="matrix(2,0,0,2,704.58596,-216.22311)"> | ||||
|       <path | ||||
|          sodipodi:nodetypes="ccccccccc" | ||||
|          inkscape:connector-curvature="0" | ||||
|          id="path5248" | ||||
|          d="m -300,593.25506 0,-40 c 0,-5 5,-10 10,-10 l 40,0 c 5,0 10,5 10,10 l 0,40 c 0,5 -5,10 -10,10 l -40,0 c -5,0 -10,-5 -10,-10 z" | ||||
|          style="fill:#808080;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> | ||||
|       <text | ||||
|          sodipodi:linespacing="125%" | ||||
|          id="text5250" | ||||
|          y="594.78485" | ||||
|          x="-289.54495" | ||||
|          style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:20px;line-height:125%;font-family:'Arial Black';-inkscape-font-specification:'Arial Black, ';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|          xml:space="preserve"><tspan | ||||
|            style="font-size:60px;fill:#ffffff" | ||||
|            y="594.78485" | ||||
|            x="-289.54495" | ||||
|            id="tspan5252" | ||||
|            sodipodi:role="line">?</tspan></text> | ||||
|     </g> | ||||
|     <g | ||||
|        transform="matrix(2,0,0,2,1040.3002,-373.36597)" | ||||
|        id="g5267"> | ||||
|       <path | ||||
|          style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|          d="m -363.73589,562.32313 0,8.92857 60,31.25 0,-8.92857 -60,-31.25" | ||||
|          id="path5269" | ||||
|          inkscape:connector-curvature="0" /> | ||||
|       <path | ||||
|          inkscape:connector-curvature="0" | ||||
|          id="path5271" | ||||
|          d="m -363.73589,602.5017 0,-8.92857 60,-31.25 0,8.92857 -60,31.25" | ||||
|          style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> | ||||
|       <rect | ||||
|          y="562.32312" | ||||
|          x="-338.91446" | ||||
|          height="25" | ||||
|          width="10" | ||||
|          id="rect5273" | ||||
|          style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.4000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | ||||
|       <g | ||||
|          id="g5275" | ||||
|          transform="translate(-583.91446,359.96093)"> | ||||
|         <rect | ||||
|            style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.4000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | ||||
|            id="rect5277" | ||||
|            width="13.686545" | ||||
|            height="40" | ||||
|            x="243.03046" | ||||
|            y="202.23593" /> | ||||
|         <rect | ||||
|            style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.4000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | ||||
|            id="rect5279" | ||||
|            width="60" | ||||
|            height="10" | ||||
|            x="220" | ||||
|            y="217.3622" /> | ||||
|         <rect | ||||
|            style="opacity:1;fill:#aa0000;fill-opacity:1;stroke:none;stroke-width:2.4000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | ||||
|            id="rect5281" | ||||
|            width="10" | ||||
|            height="40" | ||||
|            x="245" | ||||
|            y="202.3622" /> | ||||
|         <rect | ||||
|            style="opacity:1;fill:#aa0000;fill-opacity:1;stroke:none;stroke-width:2.4000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | ||||
|            id="rect5283" | ||||
|            width="60" | ||||
|            height="7.2220807" | ||||
|            x="220" | ||||
|            y="218.87743" /> | ||||
|       </g> | ||||
|       <path | ||||
|          inkscape:connector-curvature="0" | ||||
|          id="path5285" | ||||
|          d="m -363.7012,564.04753 59.9778,31.18846 0,5.42957 -60.23034,-31.31472 z" | ||||
|          style="fill:#aa0000;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> | ||||
|       <path | ||||
|          style="fill:#aa0000;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" | ||||
|          d="m -303.97594,564.04753 -59.9778,31.18846 0,5.42957 60.23034,-31.31472 z" | ||||
|          id="path5287" | ||||
|          inkscape:connector-curvature="0" /> | ||||
|     </g> | ||||
|   </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 23 KiB | 
							
								
								
									
										
											BIN
										
									
								
								dummies/occupants.xcf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								dummies/occupants.xcf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								dummies/occupants_800.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								dummies/occupants_800.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 91 KiB | 
							
								
								
									
										
											BIN
										
									
								
								dummies/occupants_800.xcf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								dummies/occupants_800.xcf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										16
									
								
								env/dev/clj/user.clj
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								env/dev/clj/user.clj
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | |||
| (ns user | ||||
|   (:require [mount.core :as mount] | ||||
|             [youyesyet.figwheel :refer [start-fw stop-fw cljs]] | ||||
|             youyesyet.core)) | ||||
| 
 | ||||
| (defn start [] | ||||
|   (mount/start-without #'youyesyet.core/repl-server)) | ||||
| 
 | ||||
| (defn stop [] | ||||
|   (mount/stop-except #'youyesyet.core/repl-server)) | ||||
| 
 | ||||
| (defn restart [] | ||||
|   (stop) | ||||
|   (start)) | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										60
									
								
								env/dev/clj/youyesyet/core.clj
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								env/dev/clj/youyesyet/core.clj
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | |||
| (ns youyesyet.core | ||||
|   (:require [youyesyet.handler :as handler] | ||||
|             [luminus.repl-server :as repl] | ||||
|             [luminus.http-server :as http] | ||||
|             [luminus-migrations.core :as migrations] | ||||
|             [youyesyet.config :refer [env]] | ||||
|             [clojure.tools.cli :refer [parse-opts]] | ||||
|             [clojure.tools.logging :as log] | ||||
|             [mount.core :as mount]) | ||||
|   (:gen-class)) | ||||
| 
 | ||||
| (def cli-options | ||||
|   [["-p" "--port PORT" "Port number" | ||||
|     :parse-fn #(Integer/parseInt %)]]) | ||||
| 
 | ||||
| (mount/defstate ^{:on-reload :noop} | ||||
|                 http-server | ||||
|                 :start | ||||
|                 (http/start | ||||
|                   (-> env | ||||
|                       (assoc :handler handler/app) | ||||
|                       (update :port #(or (-> env :options :port) %)))) | ||||
|                 :stop | ||||
|                 (http/stop http-server)) | ||||
| 
 | ||||
| (mount/defstate ^{:on-reload :noop} | ||||
|                 repl-server | ||||
|                 :start | ||||
|                 (when-let [nrepl-port (env :nrepl-port)] | ||||
|                   (repl/start {:port nrepl-port})) | ||||
|                 :stop | ||||
|                 (when repl-server | ||||
|                   (repl/stop repl-server))) | ||||
| 
 | ||||
| 
 | ||||
| (defn init-jndi [] | ||||
|   (System/setProperty "java.naming.factory.initial" | ||||
|                       "org.apache.naming.java.javaURLContextFactory") | ||||
|   (System/setProperty "java.naming.factory.url.pkgs" | ||||
|                       "org.apache.naming")) | ||||
| 
 | ||||
| (defn start-app [args] | ||||
|   (init-jndi) | ||||
|   (doseq [component (-> args | ||||
|                         (parse-opts cli-options) | ||||
|                         mount/start-with-args | ||||
|                         :started)] | ||||
|     (log/info component "started")) | ||||
|   (.addShutdownHook (Runtime/getRuntime) (Thread. handler/destroy))) | ||||
| 
 | ||||
| (defn -main [& args] | ||||
|   (cond | ||||
|     (some #{"migrate" "rollback"} args) | ||||
|     (do | ||||
|       (mount/start #'youyesyet.config/env) | ||||
|       (migrations/migrate args (select-keys env [:database-url])) | ||||
|       (System/exit 0)) | ||||
|     :else | ||||
|     (start-app args))) | ||||
|    | ||||
							
								
								
									
										10
									
								
								env/dev/clj/youyesyet/dev_middleware.clj
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								env/dev/clj/youyesyet/dev_middleware.clj
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| (ns youyesyet.dev-middleware | ||||
|   (:require [ring.middleware.reload :refer [wrap-reload]] | ||||
|             [selmer.middleware :refer [wrap-error-page]] | ||||
|             [prone.middleware :refer [wrap-exceptions]])) | ||||
| 
 | ||||
| (defn wrap-dev [handler] | ||||
|   (-> handler | ||||
|       wrap-reload | ||||
|       wrap-error-page | ||||
|       wrap-exceptions)) | ||||
							
								
								
									
										14
									
								
								env/dev/clj/youyesyet/env.clj
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								env/dev/clj/youyesyet/env.clj
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| (ns youyesyet.env | ||||
|   (:require [selmer.parser :as parser] | ||||
|             [clojure.tools.logging :as log] | ||||
|             [youyesyet.dev-middleware :refer [wrap-dev]])) | ||||
| 
 | ||||
| (def defaults | ||||
|   {:init | ||||
|    (fn [] | ||||
|      (parser/cache-off!) | ||||
|      (log/info "\n-=[youyesyet started successfully using the development profile]=-")) | ||||
|    :stop | ||||
|    (fn [] | ||||
|      (log/info "\n-=[youyesyet has shut down successfully]=-")) | ||||
|    :middleware wrap-dev}) | ||||
							
								
								
									
										12
									
								
								env/dev/clj/youyesyet/figwheel.clj
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								env/dev/clj/youyesyet/figwheel.clj
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| (ns youyesyet.figwheel | ||||
|   (:require [figwheel-sidecar.repl-api :as ra])) | ||||
| 
 | ||||
| (defn start-fw [] | ||||
|   (ra/start-figwheel!)) | ||||
| 
 | ||||
| (defn stop-fw [] | ||||
|   (ra/stop-figwheel!)) | ||||
| 
 | ||||
| (defn cljs [] | ||||
|   (ra/cljs-repl)) | ||||
| 
 | ||||
							
								
								
									
										14
									
								
								env/dev/cljs/youyesyet/dev.cljs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								env/dev/cljs/youyesyet/dev.cljs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| (ns ^:figwheel-no-load youyesyet.app | ||||
|   (:require [youyesyet.core :as core] | ||||
|             [devtools.core :as devtools] | ||||
|             [figwheel.client :as figwheel :include-macros true])) | ||||
| 
 | ||||
| (enable-console-print!) | ||||
| 
 | ||||
| (figwheel/watch-and-reload | ||||
|   :websocket-url "ws://localhost:3449/figwheel-ws" | ||||
|   :on-jsload core/mount-components) | ||||
| 
 | ||||
| (devtools/install!) | ||||
| 
 | ||||
| (core/init!) | ||||
							
								
								
									
										4
									
								
								env/dev/resources/config.edn
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								env/dev/resources/config.edn
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| {:dev true | ||||
|  :port 3000 | ||||
|  ;; when :nrepl-port is set the application starts the nREPL server on load | ||||
|  :nrepl-port 7000} | ||||
							
								
								
									
										51
									
								
								env/dev/resources/logback.xml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								env/dev/resources/logback.xml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,51 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <configuration> | ||||
|     <statusListener class="ch.qos.logback.core.status.NopStatusListener" /> | ||||
|     <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | ||||
|         <!-- encoders are assigned the type | ||||
|              ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --> | ||||
|         <encoder> | ||||
|             <charset>UTF-8</charset> | ||||
|             <pattern>%date{ISO8601} [%thread] %-5level %logger{36} - %msg %n</pattern> | ||||
|         </encoder> | ||||
|     </appender> | ||||
|     <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> | ||||
|         <file>log/youyesyet.log</file> | ||||
|         <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> | ||||
|             <fileNamePattern>log/youyesyet.%d{yyyy-MM-dd}.%i.log</fileNamePattern> | ||||
|             <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> | ||||
|                 <maxFileSize>100MB</maxFileSize> | ||||
|             </timeBasedFileNamingAndTriggeringPolicy> | ||||
|             <!-- keep 30 days of history --> | ||||
|             <maxHistory>30</maxHistory> | ||||
|         </rollingPolicy> | ||||
|         <encoder> | ||||
|             <charset>UTF-8</charset> | ||||
|             <pattern>%date{ISO8601} [%thread] %-5level %logger{36} - %msg %n</pattern> | ||||
|         </encoder> | ||||
|     </appender> | ||||
|     <logger name="org.apache.http" level="warn"> | ||||
|         <AppenderRef ref="STDOUT"/> | ||||
|         <AppenderRef ref="FILE"/> | ||||
|     </logger> | ||||
|     <logger name="org.xnio.nio" level="warn"> | ||||
|         <AppenderRef ref="STDOUT"/> | ||||
|         <AppenderRef ref="FILE"/> | ||||
|     </logger> | ||||
|     <logger name="com.zaxxer.hikari" level="warn"> | ||||
|         <AppenderRef ref="STDOUT"/> | ||||
|         <AppenderRef ref="FILE"/> | ||||
|     </logger> | ||||
|     <logger name="com.mchange" level="warn"> | ||||
|         <AppenderRef ref="STDOUT"/> | ||||
|         <AppenderRef ref="FILE"/> | ||||
|     </logger> | ||||
|     <logger name="org.eclipse.jetty" level="warn"> | ||||
|         <AppenderRef ref="STDOUT"/> | ||||
|         <AppenderRef ref="FILE"/> | ||||
|     </logger> | ||||
|     <root level="DEBUG"> | ||||
|         <appender-ref ref="STDOUT" /> | ||||
|         <appender-ref ref="FILE" /> | ||||
|     </root> | ||||
| </configuration> | ||||
							
								
								
									
										11
									
								
								env/prod/clj/youyesyet/env.clj
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								env/prod/clj/youyesyet/env.clj
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| (ns youyesyet.env | ||||
|   (:require [clojure.tools.logging :as log])) | ||||
| 
 | ||||
| (def defaults | ||||
|   {:init | ||||
|    (fn [] | ||||
|      (log/info "\n-=[youyesyet started successfully]=-")) | ||||
|    :stop | ||||
|    (fn [] | ||||
|      (log/info "\n-=[youyesyet has shut down successfully]=-")) | ||||
|    :middleware identity}) | ||||
							
								
								
									
										7
									
								
								env/prod/cljs/youyesyet/prod.cljs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								env/prod/cljs/youyesyet/prod.cljs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| (ns youyesyet.app | ||||
|   (:require [youyesyet.core :as core])) | ||||
| 
 | ||||
| ;;ignore println statements in prod | ||||
| (set! *print-fn* (fn [& _])) | ||||
| 
 | ||||
| (core/init!) | ||||
							
								
								
									
										2
									
								
								env/prod/resources/config.edn
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								env/prod/resources/config.edn
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| {:production true | ||||
|  :port 3000} | ||||
							
								
								
									
										37
									
								
								env/prod/resources/logback.xml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								env/prod/resources/logback.xml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <configuration> | ||||
|     <statusListener class="ch.qos.logback.core.status.NopStatusListener" /> | ||||
|     <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> | ||||
|         <file>log/youyesyet.log</file> | ||||
|         <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> | ||||
|             <fileNamePattern>log/youyesyet.%d{yyyy-MM-dd}.%i.log</fileNamePattern> | ||||
|             <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> | ||||
|                 <maxFileSize>100MB</maxFileSize> | ||||
|             </timeBasedFileNamingAndTriggeringPolicy> | ||||
|             <!-- keep 30 days of history --> | ||||
|             <maxHistory>30</maxHistory> | ||||
|         </rollingPolicy> | ||||
|         <encoder> | ||||
|             <charset>UTF-8</charset> | ||||
|             <pattern>%date{ISO8601} [%thread] %-5level %logger{36} - %msg %n</pattern> | ||||
|         </encoder> | ||||
|     </appender> | ||||
|     <logger name="org.apache.http" level="warn"> | ||||
|         <AppenderRef ref="FILE"/> | ||||
|     </logger> | ||||
|     <logger name="org.xnio.nio" level="warn"> | ||||
|         <AppenderRef ref="FILE"/> | ||||
|     </logger> | ||||
|     <logger name="com.zaxxer.hikari" level="warn"> | ||||
|         <AppenderRef ref="FILE"/> | ||||
|     </logger> | ||||
|     <logger name="com.mchange" level="warn"> | ||||
|         <AppenderRef ref="FILE"/> | ||||
|     </logger> | ||||
|     <logger name="org.eclipse.jetty" level="warn"> | ||||
|         <AppenderRef ref="FILE"/> | ||||
|     </logger> | ||||
|     <root level="INFO"> | ||||
|         <appender-ref ref="FILE" /> | ||||
|     </root> | ||||
| </configuration> | ||||
							
								
								
									
										3
									
								
								env/test/resources/config.edn
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								env/test/resources/config.edn
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| {:test true | ||||
|  :port 3001 | ||||
|  :nrepl-port 7001} ;; when :nrepl-port is set the application starts the nREPL server on load | ||||
							
								
								
									
										149
									
								
								project.clj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								project.clj
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,149 @@ | |||
| (defproject youyesyet "0.1.0-SNAPSHOT" | ||||
| 
 | ||||
|   :description "FIXME: write description" | ||||
|   :url "http://example.com/FIXME" | ||||
| 
 | ||||
|   :dependencies [[ring/ring-servlet "1.4.0"] | ||||
|                  [clj-oauth "1.5.4"] | ||||
|                  [ch.qos.logback/logback-classic "1.1.7"] | ||||
|                  [re-frame "0.8.0"] | ||||
|                  [cljs-ajax "0.5.8"] | ||||
|                  [secretary "1.2.3"] | ||||
|                  [reagent-utils "0.2.0"] | ||||
|                  [reagent "0.6.0"] | ||||
|                  [org.clojure/clojurescript "1.9.229" :scope "provided"] | ||||
|                  [org.clojure/clojure "1.8.0"] | ||||
|                  [korma "0.4.0"] | ||||
|                  [selmer "1.0.9"] | ||||
|                  [markdown-clj "0.9.89"] | ||||
|                  [ring-middleware-format "0.7.0"] | ||||
|                  [metosin/ring-http-response "0.8.0"] | ||||
|                  [bouncer "1.0.0"] | ||||
|                  [org.webjars/bootstrap "4.0.0-alpha.3"] | ||||
|                  [org.webjars/font-awesome "4.6.3"] | ||||
|                  [org.webjars.bower/tether "1.3.7"] | ||||
|                  [org.clojure/tools.logging "0.3.1"] | ||||
|                  [compojure "1.5.1"] | ||||
|                  [ring-webjars "0.1.1"] | ||||
|                  [ring/ring-defaults "0.2.1"] | ||||
|                  [luminus/ring-ttl-session "0.3.1"] | ||||
|                  [mount "0.1.10"] | ||||
|                  [cprop "0.1.9"] | ||||
|                  [org.clojure/tools.cli "0.3.5"] | ||||
|                  [luminus-nrepl "0.1.4"] | ||||
|                  [luminus-migrations "0.2.7"] | ||||
|                  [conman "0.6.1"] | ||||
|                  [org.postgresql/postgresql "9.4.1211"] | ||||
|                  ] | ||||
| 
 | ||||
|   :min-lein-version "2.0.0" | ||||
| 
 | ||||
|   :license {:name "GNU General Public License v2" | ||||
|             :url "http://www.gnu.org/licenses/gpl-2.0.html"} | ||||
| 
 | ||||
|   :jvm-opts ["-server" "-Dconf=.lein-env"] | ||||
|   :source-paths ["src/clj" "src/cljc"] | ||||
|   :resource-paths ["resources" "target/cljsbuild"] | ||||
|   :target-path "target/%s/" | ||||
|   :main youyesyet.core | ||||
|   :migratus {:store :database :db ~(get (System/getenv) "DATABASE_URL")} | ||||
| 
 | ||||
|   :plugins [[lein-cprop "1.0.1"] | ||||
|             [migratus-lein "0.4.2"] | ||||
|             [org.clojars.punkisdead/lein-cucumber "1.0.5"] | ||||
|             [lein-cljsbuild "1.1.4"] | ||||
|             [lein-uberwar "0.2.0"]] | ||||
|   :cucumber-feature-paths ["test/clj/features"] | ||||
| 
 | ||||
|   :uberwar | ||||
|   {:handler youyesyet.handler/app | ||||
|    :init youyesyet.handler/init | ||||
|    :destroy youyesyet.handler/destroy | ||||
|    :name "youyesyet.war"} | ||||
| 
 | ||||
|   :clean-targets ^{:protect false} | ||||
|   [:target-path [:cljsbuild :builds :app :compiler :output-dir] [:cljsbuild :builds :app :compiler :output-to]] | ||||
|   :figwheel | ||||
|   {:http-server-root "public" | ||||
|    :nrepl-port 7002 | ||||
|    :css-dirs ["resources/public/css"] | ||||
|    :nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]} | ||||
| 
 | ||||
| 
 | ||||
|   :profiles | ||||
|   {:uberjar {:omit-source true | ||||
|              :prep-tasks ["compile" ["cljsbuild" "once" "min"]] | ||||
|              :cljsbuild | ||||
|              {:builds | ||||
|               {:min | ||||
|                {:source-paths ["src/cljc" "src/cljs" "env/prod/cljs"] | ||||
|                 :compiler | ||||
|                 {:output-to "target/cljsbuild/public/js/app.js" | ||||
|                  :externs ["react/externs/react.js"] | ||||
|                  :optimizations :advanced | ||||
|                  :pretty-print false | ||||
|                  :closure-warnings | ||||
|                  {:externs-validation :off :non-standard-jsdoc :off}}}}} | ||||
| 
 | ||||
| 
 | ||||
|              :aot :all | ||||
|              :uberjar-name "youyesyet.jar" | ||||
|              :source-paths ["env/prod/clj"] | ||||
|              :resource-paths ["env/prod/resources"]} | ||||
| 
 | ||||
|    :dev           [:project/dev :profiles/dev] | ||||
|    :test          [:project/dev :project/test :profiles/test] | ||||
| 
 | ||||
|    :project/dev  {:dependencies [[prone "1.1.2"] | ||||
|                                  [ring/ring-mock "0.3.0"] | ||||
|                                  [ring/ring-devel "1.5.0"] | ||||
|                                  [luminus-jetty "0.1.4"] | ||||
|                                  [pjstadig/humane-test-output "0.8.1"] | ||||
|                                  [org.clojure/core.cache "0.6.3"] | ||||
|                                  [org.apache.httpcomponents/httpcore "4.4"] | ||||
|                                  [clj-webdriver/clj-webdriver "0.7.2"] | ||||
|                                  [org.seleniumhq.selenium/selenium-server "2.48.2"] | ||||
|                                  [doo "0.1.7"] | ||||
|                                  [binaryage/devtools "0.8.2"] | ||||
|                                  [figwheel-sidecar "0.5.8"] | ||||
|                                  [com.cemerick/piggieback "0.2.2-SNAPSHOT"] | ||||
|                                  [directory-naming/naming-java "0.8"]] | ||||
|                   :plugins      [[com.jakemccrary/lein-test-refresh "0.14.0"] | ||||
|                                  [lein-doo "0.1.7"] | ||||
|                                  [lein-figwheel "0.5.8"] | ||||
|                                  [org.clojure/clojurescript "1.9.229"]] | ||||
|                   :cljsbuild | ||||
|                   {:builds | ||||
|                    {:app | ||||
|                     {:source-paths ["src/cljs" "src/cljc" "env/dev/cljs"] | ||||
|                      :compiler | ||||
|                      {:main "youyesyet.app" | ||||
|                       :asset-path "/js/out" | ||||
|                       :output-to "target/cljsbuild/public/js/app.js" | ||||
|                       :output-dir "target/cljsbuild/public/js/out" | ||||
|                       :source-map true | ||||
|                       :optimizations :none | ||||
|                       :pretty-print true}}}} | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|                   :doo {:build "test"} | ||||
|                   :source-paths ["env/dev/clj" "test/clj"] | ||||
|                   :resource-paths ["env/dev/resources"] | ||||
|                   :repl-options {:init-ns user} | ||||
|                   :injections [(require 'pjstadig.humane-test-output) | ||||
|                                (pjstadig.humane-test-output/activate!)]} | ||||
|    :project/test {:resource-paths ["env/dev/resources" "env/test/resources"] | ||||
|                   :cljsbuild | ||||
|                   {:builds | ||||
|                    {:test | ||||
|                     {:source-paths ["src/cljc" "src/cljs" "test/cljs"] | ||||
|                      :compiler | ||||
|                      {:output-to "target/test.js" | ||||
|                       :main "youyesyet.doo-runner" | ||||
|                       :optimizations :whitespace | ||||
|                       :pretty-print true}}}} | ||||
| 
 | ||||
|                   } | ||||
|    :profiles/dev {} | ||||
|    :profiles/test {}}) | ||||
							
								
								
									
										35
									
								
								resources/docs/docs.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								resources/docs/docs.md
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | |||
| <div class="bs-callout bs-callout-danger"> | ||||
| 
 | ||||
| ### Database Configuration is Required | ||||
| 
 | ||||
| If you haven't already, then please follow the steps below to configure your database connection and run the necessary migrations. | ||||
| 
 | ||||
| * Create the database for your application. | ||||
| * Update the connection URL in the `profiles.clj` file with your database name and login. | ||||
| * Run `lein run migrate` in the root of the project to create the tables. | ||||
| * Let `mount` know to start the database connection by `require`-ing youyesyet.db.core in some other namespace. | ||||
| * Restart the application. | ||||
| 
 | ||||
| </div> | ||||
| 
 | ||||
| 
 | ||||
| ### Managing Your Middleware | ||||
| 
 | ||||
| Request middleware functions are located under the `youyesyet.middleware` namespace. | ||||
| 
 | ||||
| This namespace is reserved for any custom middleware for the application. Some default middleware is | ||||
| already defined here. The middleware is assembled in the `wrap-base` function. | ||||
| 
 | ||||
| Middleware used for development is placed in the `youyesyet.dev-middleware` namespace found in | ||||
| the `env/dev/clj/` source path. | ||||
| 
 | ||||
| ### Here are some links to get started | ||||
| 
 | ||||
| 1. [HTML templating](http://www.luminusweb.net/docs/html_templating.md) | ||||
| 2. [Accessing the database](http://www.luminusweb.net/docs/database.md) | ||||
| 3. [Setting response types](http://www.luminusweb.net/docs/responses.md) | ||||
| 4. [Defining routes](http://www.luminusweb.net/docs/routes.md) | ||||
| 5. [Adding middleware](http://www.luminusweb.net/docs/middleware.md) | ||||
| 6. [Sessions and cookies](http://www.luminusweb.net/docs/sessions_cookies.md) | ||||
| 7. [Security](http://www.luminusweb.net/docs/security.md) | ||||
| 8. [Deploying the application](http://www.luminusweb.net/docs/deployment.md) | ||||
|  | @ -0,0 +1 @@ | |||
| DROP TABLE users; | ||||
|  | @ -0,0 +1,9 @@ | |||
| CREATE TABLE users | ||||
| (id VARCHAR(20) PRIMARY KEY, | ||||
|  first_name VARCHAR(30), | ||||
|  last_name VARCHAR(30), | ||||
|  email VARCHAR(30), | ||||
|  admin BOOLEAN, | ||||
|  last_login TIME, | ||||
|  is_active BOOLEAN, | ||||
|  pass VARCHAR(300)); | ||||
							
								
								
									
										68
									
								
								resources/public/css/screen.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								resources/public/css/screen.css
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,68 @@ | |||
| html, | ||||
| body { | ||||
| 	font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; | ||||
|     height: 100%; | ||||
| } | ||||
| .navbar { | ||||
|   margin-bottom: 10px; | ||||
| } | ||||
| .navbar-brand { | ||||
|   float: none; | ||||
| } | ||||
| .navbar-nav .nav-item { | ||||
|   float: none; | ||||
| } | ||||
| .navbar-divider, | ||||
| .navbar-nav .nav-item+.nav-item, | ||||
| .navbar-nav .nav-link + .nav-link { | ||||
|   margin-left: 0; | ||||
| } | ||||
| @media (min-width: 34em) { | ||||
|   .navbar-brand { | ||||
|     float: left; | ||||
|   } | ||||
|   .navbar-nav .nav-item { | ||||
|     float: left; | ||||
|   } | ||||
|   .navbar-divider, | ||||
|   .navbar-nav .nav-item+.nav-item, | ||||
|   .navbar-nav .nav-link + .nav-link { | ||||
|     margin-left: 1rem; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @-moz-keyframes three-quarters-loader { | ||||
|     0% { | ||||
|         -moz-transform: rotate(0deg); | ||||
|         transform: rotate(0deg); | ||||
|     } | ||||
|     100% { | ||||
|         -moz-transform: rotate(360deg); | ||||
|         transform: rotate(360deg); | ||||
|     } | ||||
| } | ||||
| @-webkit-keyframes three-quarters-loader { | ||||
|     0% { | ||||
|         -webkit-transform: rotate(0deg); | ||||
|         transform: rotate(0deg); | ||||
|     } | ||||
|     100% { | ||||
|         -webkit-transform: rotate(360deg); | ||||
|         transform: rotate(360deg); | ||||
|     } | ||||
| } | ||||
| @keyframes three-quarters-loader { | ||||
|     0% { | ||||
|         -moz-transform: rotate(0deg); | ||||
|         -ms-transform: rotate(0deg); | ||||
|         -webkit-transform: rotate(0deg); | ||||
|         transform: rotate(0deg); | ||||
|     } | ||||
|     100% { | ||||
|         -moz-transform: rotate(360deg); | ||||
|         -ms-transform: rotate(360deg); | ||||
|         -webkit-transform: rotate(360deg); | ||||
|         transform: rotate(360deg); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										0
									
								
								resources/public/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								resources/public/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										21
									
								
								resources/sql/queries.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								resources/sql/queries.sql
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | |||
| -- :name create-user! :! :n | ||||
| -- :doc creates a new user record | ||||
| INSERT INTO users | ||||
| (id, first_name, last_name, email, pass) | ||||
| VALUES (:id, :first_name, :last_name, :email, :pass) | ||||
| 
 | ||||
| -- :name update-user! :! :n | ||||
| -- :doc update an existing user record | ||||
| UPDATE users | ||||
| SET first_name = :first_name, last_name = :last_name, email = :email | ||||
| WHERE id = :id | ||||
| 
 | ||||
| -- :name get-user :? :1 | ||||
| -- :doc retrieve a user given the id. | ||||
| SELECT * FROM users | ||||
| WHERE id = :id | ||||
| 
 | ||||
| -- :name delete-user! :! :n | ||||
| -- :doc delete a user given the id | ||||
| DELETE FROM users | ||||
| WHERE id = :id | ||||
							
								
								
									
										56
									
								
								resources/templates/error.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								resources/templates/error.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | |||
| <!DOCTYPE html> | ||||
| <html> | ||||
| <head> | ||||
|     <title>Something bad happened</title> | ||||
|     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|     {% style "/assets/bootstrap/css/bootstrap.min.css" %} | ||||
|     {% style "/assets/bootstrap/css/bootstrap-theme.min.css" %} | ||||
|     <style type="text/css"> | ||||
|         html { | ||||
|             height: 100%; | ||||
|             min-height: 100%; | ||||
|             min-width: 100%; | ||||
|             overflow: hidden; | ||||
|             width: 100%; | ||||
|         } | ||||
|         html body { | ||||
|             height: 100%; | ||||
|             margin: 0; | ||||
|             padding: 0; | ||||
|             width: 100%; | ||||
|         } | ||||
|         html .container-fluid { | ||||
|             display: table; | ||||
|             height: 100%; | ||||
|             padding: 0; | ||||
|             width: 100%; | ||||
|         } | ||||
|         html .row-fluid { | ||||
|             display: table-cell; | ||||
|             height: 100%; | ||||
|             vertical-align: middle; | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
| <body> | ||||
| <div class="container-fluid"> | ||||
|     <div class="row-fluid"> | ||||
|         <div class="col-lg-12"> | ||||
|             <div class="centering text-center"> | ||||
|                 <div class="text-center"> | ||||
|                     <h1><span class="text-danger">Error: {{status}}</span></h1> | ||||
|                     <hr> | ||||
|                     {% if title %} | ||||
|                     <h2 class="without-margin">{{title}}</h2> | ||||
|                     {% endif %} | ||||
|                     {% if message %} | ||||
|                     <h4 class="text-danger">{{message}}</h4> | ||||
|                     {% endif %} | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										46
									
								
								resources/templates/home.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								resources/templates/home.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | |||
| <!DOCTYPE html> | ||||
| <html> | ||||
|   <head> | ||||
|       <META http-equiv="Content-Type" content="text/html; charset=UTF-8"> | ||||
|       <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
|       <title>Welcome to youyesyet</title> | ||||
|   </head> | ||||
|   <body> | ||||
|      | ||||
|     <div id="app"> | ||||
|       <div class="container-fluid"> | ||||
|         <div class="card-deck"> | ||||
|           <div class="card-block"> | ||||
|             <h4>Welcome to youyesyet</h4> | ||||
|             <p>If you're seeing this message, that means you haven't yet compiled your ClojureScript!</p> | ||||
|             <p>Please run <code>lein figwheel</code> to start the ClojureScript compiler and reload the page.</p> | ||||
|             <h4>For better ClojureScript development experience in Chrome follow these steps:</h4> | ||||
|             <ul> | ||||
|               <li>Open DevTools | ||||
|               <li>Go to Settings ("three dots" icon in the upper right corner of DevTools > Menu > Settings F1 > General > Console) | ||||
|               <li>Check-in "Enable custom formatters" | ||||
|               <li>Close DevTools | ||||
|               <li>Open DevTools | ||||
|             </ul> | ||||
|             <p>See <a href="http://www.luminusweb.net/docs/clojurescript.md">ClojureScript</a> documentation for further details.</p> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <!-- scripts and styles --> | ||||
|     {% style "/assets/bootstrap/css/bootstrap.min.css" %} | ||||
|     {% style "/assets/font-awesome/css/font-awesome.min.css" %} | ||||
|     {% style "/css/screen.css" %} | ||||
| 
 | ||||
|     <script type="text/javascript"> | ||||
|         var context = "{{servlet-context}}"; | ||||
|         var csrfToken = "{{csrf-token}}"; | ||||
|     </script> | ||||
|     {% script "/js/app.js" %} | ||||
|   </body> | ||||
| </html> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										10
									
								
								src/clj/youyesyet/config.clj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/clj/youyesyet/config.clj
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| (ns youyesyet.config | ||||
|   (:require [cprop.core :refer [load-config]] | ||||
|             [cprop.source :as source] | ||||
|             [mount.core :refer [args defstate]])) | ||||
| 
 | ||||
| (defstate env :start (load-config | ||||
|                        :merge | ||||
|                        [(args) | ||||
|                         (source/from-system-props) | ||||
|                         (source/from-env)])) | ||||
							
								
								
									
										71
									
								
								src/clj/youyesyet/db/core.clj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/clj/youyesyet/db/core.clj
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,71 @@ | |||
| (ns youyesyet.db.core | ||||
|   (:require | ||||
|     [cheshire.core :refer [generate-string parse-string]] | ||||
|     [clojure.java.jdbc :as jdbc] | ||||
|     [conman.core :as conman] | ||||
|     [youyesyet.config :refer [env]] | ||||
|     [mount.core :refer [defstate]]) | ||||
|   (:import org.postgresql.util.PGobject | ||||
|            java.sql.Array | ||||
|            clojure.lang.IPersistentMap | ||||
|            clojure.lang.IPersistentVector | ||||
|            [java.sql | ||||
|             BatchUpdateException | ||||
|             Date | ||||
|             Timestamp | ||||
|             PreparedStatement])) | ||||
| 
 | ||||
| (defstate ^:dynamic *db* | ||||
|            :start (conman/connect! {:jdbc-url (env :database-url)}) | ||||
|            :stop (conman/disconnect! *db*)) | ||||
| 
 | ||||
| (conman/bind-connection *db* "sql/queries.sql") | ||||
| 
 | ||||
| (defn to-date [^java.sql.Date sql-date] | ||||
|   (-> sql-date (.getTime) (java.util.Date.))) | ||||
| 
 | ||||
| (extend-protocol jdbc/IResultSetReadColumn | ||||
|   Date | ||||
|   (result-set-read-column [v _ _] (to-date v)) | ||||
| 
 | ||||
|   Timestamp | ||||
|   (result-set-read-column [v _ _] (to-date v)) | ||||
| 
 | ||||
|   Array | ||||
|   (result-set-read-column [v _ _] (vec (.getArray v))) | ||||
| 
 | ||||
|   PGobject | ||||
|   (result-set-read-column [pgobj _metadata _index] | ||||
|     (let [type  (.getType pgobj) | ||||
|           value (.getValue pgobj)] | ||||
|       (case type | ||||
|         "json" (parse-string value true) | ||||
|         "jsonb" (parse-string value true) | ||||
|         "citext" (str value) | ||||
|         value)))) | ||||
| 
 | ||||
| (extend-type java.util.Date | ||||
|   jdbc/ISQLParameter | ||||
|   (set-parameter [v ^PreparedStatement stmt ^long idx] | ||||
|     (.setTimestamp stmt idx (Timestamp. (.getTime v))))) | ||||
| 
 | ||||
| (defn to-pg-json [value] | ||||
|       (doto (PGobject.) | ||||
|             (.setType "jsonb") | ||||
|             (.setValue (generate-string value)))) | ||||
| 
 | ||||
| (extend-type clojure.lang.IPersistentVector | ||||
|   jdbc/ISQLParameter | ||||
|   (set-parameter [v ^java.sql.PreparedStatement stmt ^long idx] | ||||
|     (let [conn      (.getConnection stmt) | ||||
|           meta      (.getParameterMetaData stmt) | ||||
|           type-name (.getParameterTypeName meta idx)] | ||||
|       (if-let [elem-type (when (= (first type-name) \_) (apply str (rest type-name)))] | ||||
|         (.setObject stmt idx (.createArrayOf conn elem-type (to-array v))) | ||||
|         (.setObject stmt idx (to-pg-json v)))))) | ||||
| 
 | ||||
| (extend-protocol jdbc/ISQLValue | ||||
|   IPersistentMap | ||||
|   (sql-value [value] (to-pg-json value)) | ||||
|   IPersistentVector | ||||
|   (sql-value [value] (to-pg-json value))) | ||||
							
								
								
									
										279
									
								
								src/clj/youyesyet/db/schema.clj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										279
									
								
								src/clj/youyesyet/db/schema.clj
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,279 @@ | |||
| (ns youyesyet.db.schema | ||||
|   (:require [clojure.java.jdbc :as sql] | ||||
|             [korma.core :as kc] | ||||
|             [youyesyet.db.core :as yyydb])) | ||||
| 
 | ||||
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
| ;;;; | ||||
| ;;;; youyesyet.db.schema: database schema for youyesyet. | ||||
| ;;;; | ||||
| ;;;; This program is free software; you can redistribute it and/or | ||||
| ;;;; modify it under the terms of the GNU General Public License | ||||
| ;;;; as published by the Free Software Foundation; either version 2 | ||||
| ;;;; of the License, or (at your option) any later version. | ||||
| ;;;; | ||||
| ;;;; This program is distributed in the hope that it will be useful, | ||||
| ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| ;;;; GNU General Public License for more details. | ||||
| ;;;; | ||||
| ;;;; You should have received a copy of the GNU General Public License | ||||
| ;;;; along with this program; if not, write to the Free Software | ||||
| ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, | ||||
| ;;;; USA. | ||||
| ;;;; | ||||
| ;;;; Copyright (C) 2016 Simon Brooke | ||||
| ;;;; | ||||
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
| 
 | ||||
| 
 | ||||
| (defn create-districts-table! | ||||
|   "Create a table to hold the electoral districts in which electors are registered. | ||||
|   Note that, as this app is being developed for the independence referendum in which | ||||
|   polling is across the whole of Scotland, this part of the design isn't fully thought | ||||
|   through; if later adapted to general or local elections, some breakdown or hierarchy | ||||
|   of polling districts into constituencies will be required." | ||||
|   [] | ||||
|   (sql/db-do-commands | ||||
|     yyydb/*db* | ||||
|     (sql/create-table-ddl | ||||
|       :districts | ||||
|       [;; it may be necessary to have a serial abstract primary key but I suspect | ||||
|         ;; polling districts already have numbers assigned by the Electoral Commission and | ||||
|         ;; it would be sensible to use those. TODO: check. | ||||
|         [:id "integer not null primary key"] | ||||
|         [:name "varchar(64) not null"] | ||||
|         ;; TODO: it would make sense to hold polygon data for polling districts so we can reflect | ||||
|         ;; them on the map, but I haven't thought through how to do that yet. | ||||
|         ]))) | ||||
| 
 | ||||
| 
 | ||||
| (kc/defentity district | ||||
|   (pk :id) | ||||
|   (table :districts) | ||||
|   (database yyydb/*db*) | ||||
|   (entity-fields :id :name)) | ||||
| 
 | ||||
| 
 | ||||
| (defn create-addresses-table! | ||||
|   "Create a table to hold the addresses at which electors are registered." | ||||
|   [] | ||||
|   (sql/db-do-commands | ||||
|     yyydb/*db* | ||||
|     (sql/create-table-ddl | ||||
|       :addresses | ||||
|       [[:id "serial not null primary key"] | ||||
|        ;; we do NOT want to hold multiple address records for the same household. When we receive | ||||
|        ;; the electoral roll data the addresses are likely to be text fields inlined in the elector | ||||
|        ;; record; in digesting the roll data we need to split these out and resolve them against existing | ||||
|        ;; addresses in the table, creating a new address record only if there's no match. | ||||
|        [:address "varchar(256) not null unique"] | ||||
|        [:postcode "varchar(16)"] | ||||
|        [:phone "varchar(16)"] | ||||
|        ;; the electoral district within which this address exists | ||||
|        [:district "integer references districts(id)"] | ||||
|        [:latitude :real] | ||||
|        [:longitude :real]]))) | ||||
| 
 | ||||
| 
 | ||||
| (kc/defentity address | ||||
|   (pk :id) | ||||
|   (table :addresses) | ||||
|   (database yyydb/*db*) | ||||
|   (entity-fields :id :address :postcode :phone :district :latitude :longitude)) | ||||
| 
 | ||||
| 
 | ||||
| (defn create-authority-table! | ||||
|   "Create a table to hold the oauth authorities against which we with authenticate canvassers." | ||||
|   [] | ||||
|   (sql/db-do-commands | ||||
|     yyydb/*db* | ||||
|     (sql/create-table-ddl | ||||
|       :authority | ||||
|       [[:id "varchar(32) not null primary key"] | ||||
|        ;; more stuff here when I understand more | ||||
|        ]))) | ||||
| 
 | ||||
| 
 | ||||
| (kc/defentity authority | ||||
|   (pk :id) | ||||
|   (table :authorities) | ||||
|   (database yyydb/*db*) | ||||
|   (entity-fields :id :authority)) | ||||
| 
 | ||||
| 
 | ||||
| (defn create-electors-table! | ||||
|   "Create a table to hold electors data." | ||||
|   [] | ||||
|   (sql/db-do-commands | ||||
|     yyydb/*db* | ||||
|     (sql/create-table-ddl | ||||
|       :electors | ||||
|       [[:rollno "integer primary key"] | ||||
|        [:name "varchar(64) not null"] | ||||
|        [:address "integer not null references addresses(id)" ] | ||||
|        [:phone "varchar(16)"] | ||||
|        ;; we'll probably only capture email data on electors if they request a followup | ||||
|        ;; on a particular issue by email. | ||||
|        [:email "varchar(128)"]]))) | ||||
| 
 | ||||
| 
 | ||||
| (kc/defentity elector | ||||
|   (pk :id) | ||||
|   (table :districts) | ||||
|   (database yyydb/*db*) | ||||
|   (entity-fields :id :name)) | ||||
| 
 | ||||
| 
 | ||||
| (defn create-canvassers-table! | ||||
|   "Create a table to hold data on canvassers (including authentication data)." | ||||
|   [] | ||||
|   (sql/db-do-commands | ||||
|     yyydb/*db* | ||||
|     (sql/create-table-ddl | ||||
|       :canvassers | ||||
|       [[:username "varchar(32) not null primary key"] | ||||
|        [:fullname "varchar(64) not null"] | ||||
|        ;; most canvassers will be electors, we should link them: | ||||
|        [:elector "integer references electors(rollno) on delete no action"] | ||||
|        ;; but some canvassers may not be electors, so we need contact details separately: | ||||
|        [:address "integer not null references addresses(id)" ] | ||||
|        [:phone "varchar(16)"] | ||||
|        [:email "varchar(128)"] | ||||
|        ;; with which authority do we authenticate this canvasser? I do not want to hold even | ||||
|        ;; encrypted passwords locally | ||||
|        [:authority "varchar(32) not null references authority(id) on delete no action"] | ||||
|        ;; true if the canvasser is authorised to use the app; else false. This allows us to | ||||
|        ;; block canvassers we suspect of misbehaving. | ||||
|        [:authorised :boolean]]))) | ||||
| 
 | ||||
| 
 | ||||
| (defn create-visit-table! | ||||
|   "Create a table to record visits by canvassers to addresses (including virtual visits by telephone)." | ||||
|   [] | ||||
|   (sql/db-do-commands | ||||
|     yyydb/*db* | ||||
|     (sql/create-table-ddl | ||||
|       :visits | ||||
|       [[:id "serial not null primary key"] | ||||
|        [:canvasser "varchar(32) references canvassers(username) not null"] | ||||
|        [:date "timestamp with timezone not null default now()"]]))) | ||||
| 
 | ||||
| 
 | ||||
| (defn create-option-table! | ||||
|   "Create a table to record options in the vote. This app is being created for the Independence | ||||
|    referendum, which will have just two options, 'Yes' and 'No', but it might later be adapted | ||||
|    for more general political canvassing." | ||||
|   [] | ||||
|   (sql/db-do-commands | ||||
|     yyydb/*db* | ||||
|     (sql/create-table-ddl | ||||
|       :options | ||||
|       [[:option "varchar(32) not null primary key" | ||||
|         ;; To do elections you probably need party and candidate and stuff here, but | ||||
|         ;; for the referendum it's unnecessary. | ||||
|         ]]))) | ||||
| 
 | ||||
| 
 | ||||
| (defn create-option-district-table! | ||||
|   "Create a table to link options to the districts in which they are relevant. This is extremely | ||||
|    simple for the referendum: both options are relevant to all districts. This table is essentially | ||||
|    'for later expansion'." | ||||
|   [] | ||||
|   (sql/db-do-commands | ||||
|     yyydb/*db* | ||||
|     (sql/create-table-ddl | ||||
|       :optionsdistricts | ||||
|       [[:option "varchar(32) not null references options(option)"] | ||||
|        [:district "integer not null references districts(id)"]]))) | ||||
| 
 | ||||
| 
 | ||||
| (defn create-opinion-table! | ||||
|   "Create a table to record the opinion of an elector as solicited by a canvasser during a visit. | ||||
|   TODO: decide whether to insert a record in this table for 'don't knows'." | ||||
|   [] | ||||
|   (sql/db-do-commands | ||||
|     yyydb/*db* | ||||
|     (sql/create-table-ddl | ||||
|       :opinions | ||||
|       [[:id "serial primary key"] | ||||
|        ;; the elector who gave this opinion | ||||
|        [:elector "integer not null references electors(rollno)"] | ||||
|        ;; the option the elector said they were planning to vote for | ||||
|        [:option "varchar(32) not null references options(option)"] | ||||
|        [:visit "integer not null references visits(id)"]]))) | ||||
| 
 | ||||
| 
 | ||||
| (defn create-issues-table! | ||||
|   "A table for issues we predict electors may raise on the doorstep, for which we may be | ||||
|   able to provide extra information or arrange for issue-specialists to phone and talk | ||||
|   to the elector." | ||||
|   [] | ||||
|   (sql/db-do-commands | ||||
|     yyydb/*db* | ||||
|     (sql/create-table-ddl | ||||
|       :issues | ||||
|       [;; short name of this issue, e.g. 'currency', 'defence', 'pensions' | ||||
|         [:issue "varchar(32) not null primary key"] | ||||
|         ;; URL of some brief material the canvasser can use on the doorstap | ||||
|         [:url "varchar(256)"]]))) | ||||
| 
 | ||||
| 
 | ||||
| (defn create-followup-method-table! | ||||
|   "Create a table to hold reference data on followup methods." | ||||
|   [] | ||||
|   (sql/db-do-commands | ||||
|     yyydb/*db* | ||||
|     (sql/create-table-ddl | ||||
|       :followupmethod | ||||
|       [[:method "varchar(32) not null primary key"]]))) | ||||
| 
 | ||||
| 
 | ||||
| (defn create-issue-expertise-table! | ||||
|   "A table to record which canvassers have expertise in which issues, so that followup | ||||
|   requests can be directed to the right canvassers." | ||||
|   [] | ||||
|   (sql/db-do-commands | ||||
|     yyydb/*db* | ||||
|     (sql/create-table-ddl | ||||
|       :issueexpertise | ||||
|       [[:expert "varchar(32) not null references canvasser(username)"] | ||||
|        [:issue "varchar(32) not null references issues(issue)"] | ||||
|        ;; the method by which this expert can respond to electors on this issue | ||||
|        [:method "varchar 32 not null references followupmethod(method)"]]))) | ||||
| 
 | ||||
| 
 | ||||
| (defn create-followup-request-table! | ||||
|   "Create a table to record requests for followup contacts on particular issues." | ||||
|   [] | ||||
|   (sql/db-do-commands | ||||
|     yyydb/*db* | ||||
|     (sql/create-table-ddl | ||||
|       :followuprequest | ||||
|       [[:id "serial primary key"] | ||||
|        [:elector "integer not null references electors(rollno)"] | ||||
|        [:visit "integer not null references visits(id)"] | ||||
|        [:issue "varchar(32) not null references issues(issue)"] | ||||
|        ;; We probably need a followupmethod (telephone, email, postal) and, for telephone, | ||||
|        ;; convenient times but I haven't thought through how to represent this or how | ||||
|        ;; the user interface will work. | ||||
|        [:method "varchar(32) not null references followupmethod(method)"]]))) | ||||
| 
 | ||||
| 
 | ||||
| (defn create-followup-action-table! | ||||
|   "Create a table to record actions on followup requests. Record in this table are almost | ||||
|   certainly created through a desktop-style interface rather than through te app, so it's | ||||
|   reasonable that there should be narrative fields." | ||||
|   [] | ||||
|   (sql/db-do-commands | ||||
|     yyydb/*db* | ||||
|     (sql/create-table-ddl | ||||
|       :followupaction | ||||
|       [[:id "serial primary key"] | ||||
|        [:request "integer not null references followuprequest(id)"] | ||||
|        [:actor "varchar(32) not null references canvassers(username)"] | ||||
|        [:date "timestamp with timezone not null default now()"] | ||||
|        [:notes "text"] | ||||
|        ;; true if this action closes the request | ||||
|        [:closed :boolean]]))) | ||||
| 
 | ||||
							
								
								
									
										47
									
								
								src/clj/youyesyet/handler.clj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/clj/youyesyet/handler.clj
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,47 @@ | |||
| (ns youyesyet.handler | ||||
|   (:require [compojure.core :refer [routes wrap-routes]] | ||||
|             [youyesyet.layout :refer [error-page]] | ||||
|             [youyesyet.routes.home :refer [home-routes]] | ||||
|             [youyesyet.routes.oauth :refer [oauth-routes]] | ||||
|             [compojure.route :as route] | ||||
|             [youyesyet.env :refer [defaults]] | ||||
|             [mount.core :as mount] | ||||
|             [youyesyet.middleware :as middleware] | ||||
|             [clojure.tools.logging :as log] | ||||
|             [youyesyet.config :refer [env]])) | ||||
| 
 | ||||
| (mount/defstate init-app | ||||
|                 :start ((or (:init defaults) identity)) | ||||
|                 :stop  ((or (:stop defaults) identity))) | ||||
| 
 | ||||
| (defn init | ||||
|   "init will be called once when | ||||
|    app is deployed as a servlet on | ||||
|    an app server such as Tomcat | ||||
|    put any initialization code here" | ||||
|   [] | ||||
|   (doseq [component (:started (mount/start))] | ||||
|     (log/info component "started"))) | ||||
| 
 | ||||
| (defn destroy | ||||
|   "destroy will be called when your application | ||||
|    shuts down, put any clean up code here" | ||||
|   [] | ||||
|   (doseq [component (:stopped (mount/stop))] | ||||
|     (log/info component "stopped")) | ||||
|   (shutdown-agents) | ||||
|   (log/info "youyesyet has shut down!")) | ||||
| 
 | ||||
| (def app-routes | ||||
|   (routes | ||||
|     (-> #'home-routes | ||||
|         (wrap-routes middleware/wrap-csrf) | ||||
|         (wrap-routes middleware/wrap-formats)) | ||||
|     #'oauth-routes | ||||
|     (route/not-found | ||||
|       (:body | ||||
|         (error-page {:status 404 | ||||
|                      :title "page not found"}))))) | ||||
| 
 | ||||
| 
 | ||||
| (def app (middleware/wrap-base #'app-routes)) | ||||
							
								
								
									
										39
									
								
								src/clj/youyesyet/layout.clj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/clj/youyesyet/layout.clj
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | |||
| (ns youyesyet.layout | ||||
|   (:require [selmer.parser :as parser] | ||||
|             [selmer.filters :as filters] | ||||
|             [markdown.core :refer [md-to-html-string]] | ||||
|             [ring.util.http-response :refer [content-type ok]] | ||||
|             [ring.util.anti-forgery :refer [anti-forgery-field]] | ||||
|             [ring.middleware.anti-forgery :refer [*anti-forgery-token*]])) | ||||
| 
 | ||||
| 
 | ||||
| (declare ^:dynamic *app-context*) | ||||
| (parser/set-resource-path!  (clojure.java.io/resource "templates")) | ||||
| (parser/add-tag! :csrf-field (fn [_ _] (anti-forgery-field))) | ||||
| (filters/add-filter! :markdown (fn [content] [:safe (md-to-html-string content)])) | ||||
| 
 | ||||
| (defn render | ||||
|   "renders the HTML template located relative to resources/templates" | ||||
|   [template & [params]] | ||||
|   (content-type | ||||
|     (ok | ||||
|       (parser/render-file | ||||
|         template | ||||
|         (assoc params | ||||
|           :page template | ||||
|           :csrf-token *anti-forgery-token* | ||||
|           :servlet-context *app-context*))) | ||||
|     "text/html; charset=utf-8")) | ||||
| 
 | ||||
| (defn error-page | ||||
|   "error-details should be a map containing the following keys: | ||||
|    :status - error status | ||||
|    :title - error title (optional) | ||||
|    :message - detailed error message (optional) | ||||
| 
 | ||||
|    returns a response map with the error page as the body | ||||
|    and the status specified by the status key" | ||||
|   [error-details] | ||||
|   {:status  (:status error-details) | ||||
|    :headers {"Content-Type" "text/html; charset=utf-8"} | ||||
|    :body    (parser/render-file "error.html" error-details)}) | ||||
							
								
								
									
										63
									
								
								src/clj/youyesyet/middleware.clj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/clj/youyesyet/middleware.clj
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,63 @@ | |||
| (ns youyesyet.middleware | ||||
|   (:require [youyesyet.env :refer [defaults]] | ||||
|             [clojure.tools.logging :as log] | ||||
|             [youyesyet.layout :refer [*app-context* error-page]] | ||||
|             [ring.middleware.anti-forgery :refer [wrap-anti-forgery]] | ||||
|             [ring.middleware.webjars :refer [wrap-webjars]] | ||||
|             [ring.middleware.format :refer [wrap-restful-format]] | ||||
|             [youyesyet.config :refer [env]] | ||||
|             [ring-ttl-session.core :refer [ttl-memory-store]] | ||||
|             [ring.middleware.defaults :refer [site-defaults wrap-defaults]]) | ||||
|   (:import [javax.servlet ServletContext])) | ||||
| 
 | ||||
| (defn wrap-context [handler] | ||||
|   (fn [request] | ||||
|     (binding [*app-context* | ||||
|               (if-let [context (:servlet-context request)] | ||||
|                 ;; If we're not inside a servlet environment | ||||
|                 ;; (for example when using mock requests), then | ||||
|                 ;; .getContextPath might not exist | ||||
|                 (try (.getContextPath ^ServletContext context) | ||||
|                      (catch IllegalArgumentException _ context)) | ||||
|                 ;; if the context is not specified in the request | ||||
|                 ;; we check if one has been specified in the environment | ||||
|                 ;; instead | ||||
|                 (:app-context env))] | ||||
|       (handler request)))) | ||||
| 
 | ||||
| (defn wrap-internal-error [handler] | ||||
|   (fn [req] | ||||
|     (try | ||||
|       (handler req) | ||||
|       (catch Throwable t | ||||
|         (log/error t) | ||||
|         (error-page {:status 500 | ||||
|                      :title "Something very bad has happened!" | ||||
|                      :message "We've dispatched a team of highly trained gnomes to take care of the problem."}))))) | ||||
| 
 | ||||
| (defn wrap-csrf [handler] | ||||
|   (wrap-anti-forgery | ||||
|     handler | ||||
|     {:error-response | ||||
|      (error-page | ||||
|        {:status 403 | ||||
|         :title "Invalid anti-forgery token"})})) | ||||
| 
 | ||||
| (defn wrap-formats [handler] | ||||
|   (let [wrapped (wrap-restful-format | ||||
|                   handler | ||||
|                   {:formats [:json-kw :transit-json :transit-msgpack]})] | ||||
|     (fn [request] | ||||
|       ;; disable wrap-formats for websockets | ||||
|       ;; since they're not compatible with this middleware | ||||
|       ((if (:websocket? request) handler wrapped) request)))) | ||||
| 
 | ||||
| (defn wrap-base [handler] | ||||
|   (-> ((:middleware defaults) handler) | ||||
|       wrap-webjars | ||||
|       (wrap-defaults | ||||
|         (-> site-defaults | ||||
|             (assoc-in [:security :anti-forgery] false) | ||||
|             (assoc-in  [:session :store] (ttl-memory-store (* 60 30))))) | ||||
|       wrap-context | ||||
|       wrap-internal-error)) | ||||
							
								
								
									
										35
									
								
								src/clj/youyesyet/oauth.clj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/clj/youyesyet/oauth.clj
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | |||
| (ns youyesyet.oauth | ||||
|   (:require [youyesyet.config :refer [env]] | ||||
|             [oauth.client :as oauth] | ||||
|             [mount.core :refer [defstate]] | ||||
|             [clojure.tools.logging :as log])) | ||||
| 
 | ||||
| (defstate consumer | ||||
|   :start (oauth/make-consumer | ||||
|            (env :oauth-consumer-key) | ||||
|            (env :oauth-consumer-secret) | ||||
|            (env :request-token-uri) | ||||
|            (env :access-token-uri) | ||||
|            (env :authorize-uri) | ||||
|            :hmac-sha1)) | ||||
| 
 | ||||
| (defn oauth-callback-uri | ||||
|   "Generates the oauth request callback URI" | ||||
|   [{:keys [headers]}] | ||||
|   (str (headers "x-forwarded-proto") "://" (headers "host") "/oauth/twitter-callback")) | ||||
| 
 | ||||
| (defn fetch-request-token | ||||
|   "Fetches a request token." | ||||
|   [request] | ||||
|   (let [callback-uri (oauth-callback-uri request)] | ||||
|     (log/info "Fetching request token using callback-uri" callback-uri) | ||||
|     (oauth/request-token consumer (oauth-callback-uri request)))) | ||||
| 
 | ||||
| (defn fetch-access-token | ||||
|   [request_token] | ||||
|   (oauth/access-token consumer request_token (:oauth_verifier request_token))) | ||||
| 
 | ||||
| (defn auth-redirect-uri | ||||
|   "Gets the URI the user should be redirected to when authenticating." | ||||
|   [request-token] | ||||
|   (str (oauth/user-approval-uri consumer request-token))) | ||||
							
								
								
									
										15
									
								
								src/clj/youyesyet/routes/home.clj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/clj/youyesyet/routes/home.clj
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| (ns youyesyet.routes.home | ||||
|   (:require [youyesyet.layout :as layout] | ||||
|             [youyesyet.db.core :as db-core] | ||||
|             [compojure.core :refer [defroutes GET]] | ||||
|             [ring.util.http-response :as response] | ||||
|             [clojure.java.io :as io])) | ||||
| 
 | ||||
| (defn home-page [] | ||||
|   (layout/render "home.html")) | ||||
| 
 | ||||
| (defroutes home-routes | ||||
|   (GET "/" [] (home-page)) | ||||
|   (GET "/docs" [] (-> (response/ok (-> "docs/docs.md" io/resource slurp)) | ||||
|                       (response/header "Content-Type" "text/plain; charset=utf-8")))) | ||||
| 
 | ||||
							
								
								
									
										32
									
								
								src/clj/youyesyet/routes/oauth.clj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/clj/youyesyet/routes/oauth.clj
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,32 @@ | |||
| (ns youyesyet.routes.oauth | ||||
|   (:require [compojure.core :refer [defroutes GET]] | ||||
|             [ring.util.http-response :refer [ok found]] | ||||
|             [clojure.java.io :as io] | ||||
|             [youyesyet.oauth :as oauth] | ||||
|             [clojure.tools.logging :as log])) | ||||
| 
 | ||||
| (defn oauth-init | ||||
|   "Initiates the Twitter OAuth" | ||||
|   [request] | ||||
|   (-> (oauth/fetch-request-token request) | ||||
|       :oauth_token | ||||
|       oauth/auth-redirect-uri | ||||
|       found)) | ||||
| 
 | ||||
| (defn oauth-callback | ||||
|   "Handles the callback from Twitter." | ||||
|   [request_token {:keys [session]}] | ||||
|   ; oauth request was denied by user | ||||
|   (if (:denied request_token) | ||||
|     (-> (found "/") | ||||
|         (assoc :flash {:denied true})) | ||||
|     ; fetch the request token and do anything else you wanna do if not denied. | ||||
|     (let [{:keys [user_id screen_name]} (oauth/fetch-access-token request_token)] | ||||
|       (log/info "successfully authenticated as" user_id screen_name) | ||||
|       (-> (found "/") | ||||
|           (assoc :session | ||||
|             (assoc session :user-id user_id :screen-name screen_name)))))) | ||||
| 
 | ||||
| (defroutes oauth-routes | ||||
|   (GET "/oauth/oauth-init" req (oauth-init req)) | ||||
|   (GET "/oauth/oauth-callback" [& req_token :as req] (oauth-callback req_token req))) | ||||
							
								
								
									
										3
									
								
								src/cljc/youyesyet/validation.cljc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/cljc/youyesyet/validation.cljc
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| (ns youyesyet.validation | ||||
|   (:require [bouncer.core :as b] | ||||
|             [bouncer.validators :as v])) | ||||
							
								
								
									
										20
									
								
								src/cljs/youyesyet/ajax.cljs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/cljs/youyesyet/ajax.cljs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | |||
| (ns youyesyet.ajax | ||||
|   (:require [ajax.core :as ajax])) | ||||
| 
 | ||||
| (defn local-uri? [{:keys [uri]}] | ||||
|   (not (re-find #"^\w+?://" uri))) | ||||
| 
 | ||||
| (defn default-headers [request] | ||||
|   (if (local-uri? request) | ||||
|     (-> request | ||||
|         (update :uri #(str js/context %)) | ||||
|         (update :headers #(merge {"x-csrf-token" js/csrfToken} %))) | ||||
|     request)) | ||||
| 
 | ||||
| (defn load-interceptors! [] | ||||
|   (swap! ajax/default-interceptors | ||||
|          conj | ||||
|          (ajax/to-interceptor {:name "default headers" | ||||
|                                :request default-headers}))) | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										95
									
								
								src/cljs/youyesyet/core.cljs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/cljs/youyesyet/core.cljs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,95 @@ | |||
| (ns youyesyet.core | ||||
|   (:require [reagent.core :as r] | ||||
|             [re-frame.core :as rf] | ||||
|             [secretary.core :as secretary] | ||||
|             [goog.events :as events] | ||||
|             [goog.history.EventType :as HistoryEventType] | ||||
|             [markdown.core :refer [md->html]] | ||||
|             [ajax.core :refer [GET POST]] | ||||
|             [youyesyet.ajax :refer [load-interceptors!]] | ||||
|             [youyesyet.handlers] | ||||
|             [youyesyet.subscriptions]) | ||||
|   (:import goog.History)) | ||||
| 
 | ||||
| (defn nav-link [uri title page collapsed?] | ||||
|   (let [selected-page (rf/subscribe [:page])] | ||||
|     [:li.nav-item | ||||
|      {:class (when (= page @selected-page) "active")} | ||||
|      [:a.nav-link | ||||
|       {:href uri | ||||
|        :on-click #(reset! collapsed? true)} title]])) | ||||
| 
 | ||||
| (defn navbar [] | ||||
|   (r/with-let [collapsed? (r/atom true)] | ||||
|     [:nav.navbar.navbar-light.bg-faded | ||||
|      [:button.navbar-toggler.hidden-sm-up | ||||
|       {:on-click #(swap! collapsed? not)} "☰"] | ||||
|      [:div.collapse.navbar-toggleable-xs | ||||
|       (when-not @collapsed? {:class "in"}) | ||||
|       [:a.navbar-brand {:href "#/"} "youyesyet"] | ||||
|       [:ul.nav.navbar-nav | ||||
|        [nav-link "#/" "Home" :home collapsed?] | ||||
|        [nav-link "#/about" "About" :about collapsed?]]]])) | ||||
| 
 | ||||
| (defn about-page [] | ||||
|   [:div.container | ||||
|    [:div.row | ||||
|     [:div.col-md-12 | ||||
|      "this is the story of youyesyet... work in progress"]]]) | ||||
| 
 | ||||
| (defn home-page [] | ||||
|   [:div.container | ||||
|    [:div.jumbotron | ||||
|     [:h1 "Welcome to youyesyet"] | ||||
|     [:p "Time to start building your site!"] | ||||
|     [:p [:a.btn.btn-primary.btn-lg {:href "http://luminusweb.net"} "Learn more »"]]] | ||||
|    (when-let [docs @(rf/subscribe [:docs])] | ||||
|      [:div.row | ||||
|       [:div.col-md-12 | ||||
|        [:div {:dangerouslySetInnerHTML | ||||
|               {:__html (md->html docs)}}]]])]) | ||||
| 
 | ||||
| (def pages | ||||
|   {:home #'home-page | ||||
|    :about #'about-page}) | ||||
| 
 | ||||
| (defn page [] | ||||
|   [:div | ||||
|    [navbar] | ||||
|    [(pages @(rf/subscribe [:page]))]]) | ||||
| 
 | ||||
| ;; ------------------------- | ||||
| ;; Routes | ||||
| (secretary/set-config! :prefix "#") | ||||
| 
 | ||||
| (secretary/defroute "/" [] | ||||
|   (rf/dispatch [:set-active-page :home])) | ||||
| 
 | ||||
| (secretary/defroute "/about" [] | ||||
|   (rf/dispatch [:set-active-page :about])) | ||||
| 
 | ||||
| ;; ------------------------- | ||||
| ;; History | ||||
| ;; must be called after routes have been defined | ||||
| (defn hook-browser-navigation! [] | ||||
|   (doto (History.) | ||||
|     (events/listen | ||||
|       HistoryEventType/NAVIGATE | ||||
|       (fn [event] | ||||
|         (secretary/dispatch! (.-token event)))) | ||||
|     (.setEnabled true))) | ||||
| 
 | ||||
| ;; ------------------------- | ||||
| ;; Initialize app | ||||
| (defn fetch-docs! [] | ||||
|   (GET (str js/context "/docs") {:handler #(rf/dispatch [:set-docs %])})) | ||||
| 
 | ||||
| (defn mount-components [] | ||||
|   (r/render [#'page] (.getElementById js/document "app"))) | ||||
| 
 | ||||
| (defn init! [] | ||||
|   (rf/dispatch-sync [:initialize-db]) | ||||
|   (load-interceptors!) | ||||
|   (fetch-docs!) | ||||
|   (hook-browser-navigation!) | ||||
|   (mount-components)) | ||||
							
								
								
									
										4
									
								
								src/cljs/youyesyet/db.cljs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/cljs/youyesyet/db.cljs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| (ns youyesyet.db) | ||||
| 
 | ||||
| (def default-db | ||||
|   {:page :home}) | ||||
							
								
								
									
										18
									
								
								src/cljs/youyesyet/handlers.cljs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/cljs/youyesyet/handlers.cljs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| (ns youyesyet.handlers | ||||
|   (:require [youyesyet.db :as db] | ||||
|             [re-frame.core :refer [dispatch reg-event-db]])) | ||||
| 
 | ||||
| (reg-event-db | ||||
|   :initialize-db | ||||
|   (fn [_ _] | ||||
|     db/default-db)) | ||||
| 
 | ||||
| (reg-event-db | ||||
|   :set-active-page | ||||
|   (fn [db [_ page]] | ||||
|     (assoc db :page page))) | ||||
| 
 | ||||
| (reg-event-db | ||||
|   :set-docs | ||||
|   (fn [db [_ docs]] | ||||
|     (assoc db :docs docs))) | ||||
							
								
								
									
										12
									
								
								src/cljs/youyesyet/subscriptions.cljs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/cljs/youyesyet/subscriptions.cljs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| (ns youyesyet.subscriptions | ||||
|   (:require [re-frame.core :refer [reg-sub]])) | ||||
| 
 | ||||
| (reg-sub | ||||
|   :page | ||||
|   (fn [db _] | ||||
|     (:page db))) | ||||
| 
 | ||||
| (reg-sub | ||||
|   :docs | ||||
|   (fn [db _] | ||||
|     (:docs db))) | ||||
							
								
								
									
										7
									
								
								test/clj/features/index_page.feature
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								test/clj/features/index_page.feature
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| Feature: Cukes | ||||
|   An example of testing a compojure app with cucumber. | ||||
| 
 | ||||
|   Scenario: Index Page | ||||
|     Given I am at the "homepage" | ||||
|     Then I should see "Welcome to youyesyet" | ||||
| 
 | ||||
							
								
								
									
										11
									
								
								test/clj/features/step_definitions/home_page_steps.clj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								test/clj/features/step_definitions/home_page_steps.clj
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| (require '[clj-webdriver.taxi :as taxi] | ||||
|          '[youyesyet.browser :refer [browser-up browser-down]] | ||||
|          '[clojure.test :refer :all]) | ||||
| 
 | ||||
| (Given #"^I am at the \"homepage\"$" [] | ||||
|        (browser-up) | ||||
|        (taxi/to "http://localhost:3000/")) | ||||
| 
 | ||||
| (Then #"^I should see \"([^\"]*)\"$" [title] | ||||
|       (is (= (taxi/text "div.jumbotron > h1") title)) | ||||
|       (browser-down)) | ||||
							
								
								
									
										17
									
								
								test/clj/youyesyet/browser.clj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								test/clj/youyesyet/browser.clj
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| (ns youyesyet.browser | ||||
|   (:require [clj-webdriver.taxi :refer :all])) | ||||
| 
 | ||||
| (def ^:private browser-count (atom 0)) | ||||
| 
 | ||||
| (defn browser-up | ||||
|   "Start up a browser if it's not already started." | ||||
|   [] | ||||
|   (when (= 1 (swap! browser-count inc)) | ||||
|     (set-driver! {:browser :firefox}) | ||||
|     (implicit-wait 60000))) | ||||
| 
 | ||||
| (defn browser-down | ||||
|   "If this is the last request, shut the browser down." | ||||
|   [& {:keys [force] :or {force false}}] | ||||
|   (when (zero? (swap! browser-count (if force (constantly 0) dec))) | ||||
|     (quit))) | ||||
							
								
								
									
										36
									
								
								test/clj/youyesyet/test/db/core.clj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								test/clj/youyesyet/test/db/core.clj
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| (ns youyesyet.test.db.core | ||||
|   (:require [youyesyet.db.core :refer [*db*] :as db] | ||||
|             [luminus-migrations.core :as migrations] | ||||
|             [clojure.test :refer :all] | ||||
|             [clojure.java.jdbc :as jdbc] | ||||
|             [youyesyet.config :refer [env]] | ||||
|             [mount.core :as mount])) | ||||
| 
 | ||||
| (use-fixtures | ||||
|   :once | ||||
|   (fn [f] | ||||
|     (mount/start | ||||
|       #'youyesyet.config/env | ||||
|       #'youyesyet.db.core/*db*) | ||||
|     (migrations/migrate ["migrate"] (select-keys env [:database-url])) | ||||
|     (f))) | ||||
| 
 | ||||
| (deftest test-users | ||||
|   (jdbc/with-db-transaction [t-conn *db*] | ||||
|     (jdbc/db-set-rollback-only! t-conn) | ||||
|     (is (= 1 (db/create-user! | ||||
|                t-conn | ||||
|                {:id         "1" | ||||
|                 :first_name "Sam" | ||||
|                 :last_name  "Smith" | ||||
|                 :email      "sam.smith@example.com" | ||||
|                 :pass       "pass"}))) | ||||
|     (is (= {:id         "1" | ||||
|             :first_name "Sam" | ||||
|             :last_name  "Smith" | ||||
|             :email      "sam.smith@example.com" | ||||
|             :pass       "pass" | ||||
|             :admin      nil | ||||
|             :last_login nil | ||||
|             :is_active  nil} | ||||
|            (db/get-user t-conn {:id "1"}))))) | ||||
							
								
								
									
										13
									
								
								test/clj/youyesyet/test/handler.clj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								test/clj/youyesyet/test/handler.clj
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| (ns youyesyet.test.handler | ||||
|   (:require [clojure.test :refer :all] | ||||
|             [ring.mock.request :refer :all] | ||||
|             [youyesyet.handler :refer :all])) | ||||
| 
 | ||||
| (deftest test-app | ||||
|   (testing "main route" | ||||
|     (let [response (app (request :get "/"))] | ||||
|       (is (= 200 (:status response))))) | ||||
| 
 | ||||
|   (testing "not-found route" | ||||
|     (let [response (app (request :get "/invalid"))] | ||||
|       (is (= 404 (:status response)))))) | ||||
							
								
								
									
										8
									
								
								test/cljs/youyesyet/core_test.cljs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								test/cljs/youyesyet/core_test.cljs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| (ns youyesyet.core-test | ||||
|   (:require [cljs.test :refer-macros [is are deftest testing use-fixtures]] | ||||
|             [reagent.core :as reagent :refer [atom]] | ||||
|             [youyesyet.core :as rc])) | ||||
| 
 | ||||
| (deftest test-home | ||||
|   (is (= true true))) | ||||
| 
 | ||||
							
								
								
									
										6
									
								
								test/cljs/youyesyet/doo_runner.cljs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								test/cljs/youyesyet/doo_runner.cljs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| (ns youyesyet.doo-runner | ||||
|   (:require [doo.runner :refer-macros [doo-tests]] | ||||
|             [youyesyet.core-test])) | ||||
| 
 | ||||
| (doo-tests 'youyesyet.core-test) | ||||
| 
 | ||||
		Loading…
	
		Reference in a new issue