--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+
+# Actions for Opportunity web application
+module opportunity_controller
+
+import nitcorn
+import sha1
+import templates
+import opportunity_model
+
+# Any kind of opportunity `Action` (serves a request)
+abstract class OpportunityAction
+ super Action
+
+ # Path to db
+ var db_path = "opportunity"
+
+ # Returns a bad request with an error code 400
+ #
+ # TODO: Add a specific body to the bad request page.
+ fun bad_req: HttpResponse do
+ var rsp = new HttpResponse(400)
+ rsp.body = (new OpportunityHomePage).write_to_string
+ return rsp
+ end
+end
+
+# Welcome page for Opportunity
+class OpportunityWelcome
+ super OpportunityAction
+
+ redef fun answer(request, url) do
+ print "Received request for {url}"
+ var get = request.get_args
+ var rq = url.split("/")
+ if rq.has("meetup_create") then
+ var mname = request.string_arg("meetup_name")
+ var mdate = request.string_arg("meetup_date")
+ var mplace = request.string_arg("meetup_place")
+ if mname == null or mdate == null or mplace == null then return bad_req
+ var db = new OpportunityDB.open(db_path)
+ var meet = new Meetup(mname, mdate, mplace)
+ if meet == null then
+ db.close
+ return bad_req
+ end
+ meet.commit(db)
+ var ans_tmp = "answer_"
+ var cnt = 1
+ loop
+ var anss = request.string_arg(ans_tmp + cnt.to_s)
+ if anss == null then break
+ var ans = new Answer(anss)
+ ans.meetup = meet
+ ans.commit(db)
+ cnt += 1
+ end
+ db.close
+ var rsp = new HttpResponse(200)
+ if meet.id == "" then
+ rsp.body = (new MeetupCreationPage).write_to_string
+ else
+ rsp.body = (new MeetupConfirmation(meet)).write_to_string
+ end
+ return rsp
+ end
+ if rq.has("new_meetup") then
+ var rsp = new HttpResponse(200)
+ var page = new MeetupCreationPage
+ rsp.body = page.write_to_string
+ return rsp
+ end
+ if get.has_key("meetup_id") then
+ var rsp = new HttpResponse(200)
+ rsp.body = (new OpportunityMeetupPage.from_id(get["meetup_id"])).write_to_string
+ return rsp
+ end
+ var rsp = new HttpResponse(200)
+ rsp.body = (new OpportunityHomePage).write_to_string
+ return rsp
+ end
+
+end
+
+# Any kind of REST request to Opportunity
+class OpportunityRESTAction
+ super OpportunityAction
+
+ redef fun answer(request, uri) do
+ print "Received REST request from {uri}"
+ var get = request.get_args
+ var req = uri.split("/")
+ if req.has("people") then
+ return (new OpportunityPeopleREST).answer(request, uri)
+ else if req.has("answer") then
+ return (new OpportunityAnswerREST).answer(request, uri)
+ else if req.has("meetup") then
+ return (new OpportunityMeetupREST).answer(request, uri)
+ else
+ return new HttpResponse(400)
+ end
+ end
+
+end
+
+# REST Actions working on People
+class OpportunityPeopleREST
+ super OpportunityAction
+
+ redef fun answer(request, uri) do
+ # Should be DELETE for true REST API
+ # TODO : change method to DELETE once supported by Nitcorn
+ if request.method == "POST" then
+ var meth = request.string_arg("method")
+ if meth == null then return bad_req
+ if meth != "DELETE" then return bad_req
+ var pid = request.int_arg("p_id")
+ if pid == null then return bad_req
+ var db = new OpportunityDB.open(db_path)
+ db.remove_people_by_id(pid)
+ db.close
+ return new HttpResponse(200)
+ end
+ return new HttpResponse(400)
+ end
+
+end
+
+# REST Actions working on Answers
+class OpportunityAnswerREST
+ super OpportunityAction
+
+ redef fun answer(request, uri) do
+ var persid = request.int_arg("pers_id")
+ var ansid = request.int_arg("answer_id")
+ var ans = request.bool_arg("answer")
+ if persid == null or ansid == null or ans == null then return bad_req
+ var db = new OpportunityDB.open(db_path)
+ db.change_answer(ansid, persid, ans)
+ db.close
+ return new HttpResponse(200)
+ end
+end
+
+# REST Actions working on Meetups
+class OpportunityMeetupREST
+ super OpportunityAction
+
+ redef fun answer(request, uri) do
+ var args = uri.split("/")
+ if args.has("new_pers") then
+ var name = request.string_arg("persname")
+ var m_id = request.string_arg("meetup_id")
+ var ans = request.string_arg("answers").split("&")
+ if name == null or m_id == null then return bad_req
+ print ans
+ var ansmap = new HashMap[Int, Bool]
+ for i in ans do
+ var mp = i.split("=")
+ var b = false
+ if mp.last == "true" then b = true
+ var id = mp.first.split("_").last
+ if not id.is_numeric then continue
+ ansmap[id.to_i] = b
+ end
+ var db = new OpportunityDB.open(db_path)
+ var m = db.find_meetup_by_id(m_id)
+ var sublen = name.index_of(' ')
+ var rname = ""
+ var rsurname = ""
+ if sublen == -1 then
+ rsurname = name
+ else
+ rsurname = name.substring(0, sublen)
+ rname = name.substring_from(sublen + 1)
+ end
+ var p = new People(rname, rsurname)
+ for i in m.answers(db) do
+ if not ansmap.has_key(i.id) then
+ p.answers[i] = false
+ else
+ p.answers[i] = ansmap[i.id]
+ end
+ end
+ p.commit(db)
+ db.close
+ var rsp = new HttpResponse(200)
+ rsp.body = p.id.to_s
+ return rsp
+ end
+ return new HttpResponse(400)
+ end
+end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+
+# Model for the data of Opportunity
+module opportunity_model
+
+import sqlite3
+import sha1
+import serialization
+
+# A SQLiteDB object for `Opportunity`
+class OpportunityDB
+ super Sqlite3DB
+
+ init open(x) do
+ super
+
+ create_db
+ end
+
+ # Creates the tables and triggers for Opportunity (SQLite3 DB)
+ fun create_db do
+ assert create_table("IF NOT EXISTS meetups (id CHAR(40) PRIMARY KEY, name TEXT, date TEXT, place TEXT);") else
+ print error or else "?"
+ end
+ assert create_table("IF NOT EXISTS people(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, surname TEXT);") else
+ print error or else "?"
+ end
+ assert create_table("IF NOT EXISTS answers(id INTEGER PRIMARY KEY AUTOINCREMENT, meetup_id CHAR(40), name TEXT, FOREIGN KEY(meetup_id) REFERENCES meetups(id));") else
+ print error or else "?"
+ end
+ assert create_table("IF NOT EXISTS part_answers(id_part INTEGER, id_ans INTEGER, value INTEGER, FOREIGN KEY(id_part) REFERENCES people(id), FOREIGN KEY(id_ans) REFERENCES answers(id));") else
+ print error or else "?"
+ end
+ #NOTE: The following triggers could be replaced by ON DELETE CASCADE clauses
+ # Thing is, SQLite does not seem to support those operations (well, not by default, it seems
+ # we must re-compile the lib to support it. So, well, let's just create triggers heh.
+ assert execute("CREATE TRIGGER IF NOT EXISTS answers_clean AFTER DELETE ON meetups BEGIN DELETE FROM answers WHERE answers.meetup_id=OLD.id;END;") else
+ print error or else "?"
+ end
+ assert execute("CREATE TRIGGER IF NOT EXISTS ans_clean AFTER DELETE ON answers BEGIN DELETE FROM part_answers WHERE OLD.id=part_answers.id_ans;END;") else
+ print error or else "?"
+ end
+ assert execute("CREATE TRIGGER IF NOT EXISTS ppl_clean AFTER DELETE ON people BEGIN DELETE FROM part_answers WHERE OLD.id=part_answers.id_part;END;")
+ end
+
+ # Find a `People` by its id, or `null` if it could not be found
+ fun find_people_by_id(id: Int): nullable People do
+ var req = select("* from people where id={id};")
+ for i in req do
+ return new People.from_db(i[0].to_i, i[1].to_s, i[2].to_s)
+ end
+ return null
+ end
+
+ # Find a `Meetup` by its id or `null` if it could not be found
+ fun find_meetup_by_id(id: String): nullable Meetup do
+ var req = select("* FROM meetups where id={id.to_sql_string};")
+ for i in req do
+ return new Meetup.from_db(i[0].to_s, i[1].to_s, i[2].to_s, i[3].to_s)
+ end
+ return null
+ end
+
+ # Change an Answer `ansid` for someone with an id `pid` to `resp`
+ #
+ # Returns `true` if the request was sucessful, false otherwise
+ fun change_answer(pid: Int, ansid: Int, resp: Bool): Bool do
+ var rsp = 0
+ if resp then rsp = 1
+ var rq = execute("INSERT OR REPLACE INTO part_answers(id_part, id_ans, value) VALUES({pid},{ansid},{rsp});")
+ if not rq then
+ print "Error while updating answer {ansid}:{pid}"
+ print error or else "Unknown error"
+ return false
+ end
+ return true
+ end
+
+ # Removes a person in the Database by its `id`
+ #
+ # Returns true if sucessful, false otherwise
+ fun remove_people_by_id(id: Int): Bool do
+ var rq = execute("DELETE FROM people WHERE id = {id};")
+ if not rq then
+ print "Cannot delete people {id}"
+ print error or else "Unknown error"
+ return false
+ end
+ return true
+ end
+end
+
+# Any kind of Database Object that can be persisted to the database
+abstract class DBObject
+
+ # Commits the modifications done to the Object in the database
+ fun commit(db: OpportunityDB): Bool is abstract
+end
+
+# A Meetup participant, linked to the DB
+class People
+ super DBObject
+
+ # ID in the Database, -1 if not set
+ var id: Int = -1
+ # Name of the participant
+ var name: String
+ # Surname of the participant
+ var surname: String
+ # Map of the answers of a Meetup and the answers of the participant
+ var answers: Map[Answer, Bool] = new HashMap[Answer, Bool]
+
+ # To be used internally when fetching the `People` in Database
+ private init from_db(id: Int, name, surname: String) do
+ init(name, surname)
+ self.id = id
+ end
+
+ # Changes an answer `ans` (or adds it)
+ fun answer=(ans: Answer, resp: Bool) do
+ answers[ans] = resp
+ end
+
+ # Loads the answers for a Meetup
+ #
+ # NOTE: If `self` does not exist in the Database, no answers will be fetched
+ fun load_answers(db: OpportunityDB, meetup: Meetup) do
+ self.answers = new HashMap[Answer, Bool]
+ var req = db.select("answers.id, answers.name, part_answers.value FROM part_answers, answers WHERE part_answers.id_part={id} AND answers.id=part_answers.id_ans AND answers.meetup_id={meetup.id.to_sql_string} GROUP BY answers.id;")
+ for i in req do
+ var ans = new Answer.from_db(i[0].to_i, i[1].to_s)
+ answers[ans] = false
+ if i[2].to_i == 1 then answers[ans] = true
+ end
+ end
+
+ redef fun to_s do return "{surname.capitalized} {name.capitalized}"
+
+ redef fun commit(db) do
+ if id == -1 then
+ if not db.execute("INSERT INTO people (name,surname) VALUES ({name.to_sql_string}, {surname.to_sql_string});") then
+ print "Error while adding people {self}"
+ print db.error or else "Unknown error"
+ return false
+ end
+ id = db.last_insert_rowid
+ else
+ if not db.execute("UPDATE people SET name={name.to_sql_string}, surname={surname.to_sql_string} WHERE ID={id};") then
+ print "Error while updating people {self}"
+ print db.error or else "Unknown error"
+ return false
+ end
+ end
+ for i,j in answers do
+ if i.id == -1 then i.commit(db)
+ var val = 0
+ if j then val = 1
+ if not db.execute("INSERT OR REPLACE INTO part_answers(id_part, id_ans, value) VALUES ({id},{i.id},{val});") then
+ print("Error while adding/replacing part_answers {id}|{i.id}|{j}")
+ print db.error or else "Unknown error"
+ return false
+ end
+ end
+ return true
+ end
+end
+
+# A `Meetup` is an opportunity of meeting, linked to the database
+class Meetup
+ super DBObject
+
+ # ID of the meetup, SHA-1 of the informations that are contained
+ var id: String = ""
+ # Name for the meetup
+ var name: String
+ # SQLite-formatted date : YYYY:DD:MM HH:MM:SS
+ var date: String
+ # Place of the meetup
+ var place: String
+
+ # Builds the object with all the informations found in the database
+ private init from_db(id, name, date, place: String) do
+ self.id = id
+ self.name = name
+ self.date = date
+ self.place = place
+ end
+
+ # Gets the answers bound to the current `Meetup`
+ fun answers(db: OpportunityDB): Array[Answer] do
+ if id == "" then
+ return new Array[Answer]
+ end
+ var res = db.select("id, name FROM answers WHERE meetup_id={id.to_sql_string}")
+ var ans = new Array[Answer]
+ for i in res do
+ ans.add new Answer.from_db(i[0].to_i, i[1].to_s)
+ end
+ return ans
+ end
+
+ # Gets the list of the participants of a `Meetup`
+ fun participants(db: OpportunityDB): Array[People] do
+ var resp = db.select("people.* FROM people, meetups, answers, part_answers WHERE meetups.id={id.to_sql_string} AND answers.meetup_id={id.to_sql_string} AND part_answers.id_ans=answers.id AND people.id=part_answers.id_part GROUP BY people.id;")
+ var arr = new Array[People]
+ for i in resp do
+ arr.add (new People.from_db(i[0].to_i, i[1].to_s, i[2].to_s))
+ end
+ return arr
+ end
+
+ redef fun commit(db) do
+ if id == "" then
+ var tmpid = (name + date + place).sha1_to_s
+ if not db.execute("INSERT INTO meetups (id, name, date, place) VALUES({tmpid.to_sql_string}, {name.to_sql_string}, {date.to_sql_string}, {place.to_sql_string});") then
+ print "Error recording entry Meetup {self}"
+ print db.error or else "Null error"
+ return false
+ end
+ id = tmpid
+ return true
+ else
+ return db.execute("UPDATE meetups (name, date, place) VALUES({name.to_sql_string}, {date.to_sql_string}, {place.to_sql_string}) WHERE ID={id.to_sql_string};")
+ end
+ end
+
+ redef fun to_s do
+ return "Event : {name}\nWhen : {date}\nWhere : {place}"
+ end
+end
+
+# An answer linked to a Meetup in the database
+class Answer
+ super DBObject
+
+ # Name of the answer (title)
+ var name: String
+ # Id in the database, -1 if not set
+ var id: Int = -1
+ # Meetup the answer is linked to (null while it is not added in the database or set via API)
+ var meetup: nullable Meetup = null is writable
+
+ # To be used internally when fetching the object from Database
+ private init from_db(id: Int, name: String) do
+ init name
+ self.id = id
+ end
+
+ # Loads the Meetup associated to `self`
+ #
+ # REQUIRE: is loaded in database
+ fun load_meetup(db: OpportunityDB): Meetup do
+ assert id != null
+ var res = db.select("meetups.* FROM meetups, answers WHERE answers.id={id} AND answers.meetup_id=meetups.id;")
+ for i in res do
+ return new Meetup.from_db(i[0].to_s, i[1].to_s, i[2].to_s, i[3].to_s)
+ end
+ # If no Meetup could be loaded, the contract was not respected
+ abort
+ end
+
+ redef fun commit(db) do
+ var m = meetup
+ if m == null then return false
+ if m.id == "" then
+ if not m.commit(db) then
+ print "Error when creating meetup {m}"
+ return false
+ end
+ end
+ if id == -1 then
+ if not db.execute("INSERT INTO answers (name, meetup_id) VALUES({name.to_sql_string}, {m.id.to_sql_string});") then
+ print "Cannot create {self} in database"
+ print db.error or else "Unknown error"
+ return false
+ end
+ id = db.last_insert_rowid
+ else
+ if not db.execute("UPDATE answers (name) VALUES ({name.to_sql_string}) WHERE meetup_id={m.id.to_sql_string};") then
+ print "Error updating {self} in database"
+ print db.error or else "Unknown error"
+ return false
+ end
+ end
+ return true
+ end
+
+ redef fun to_s do return name
+end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+
+# Web server for Opportunity : A meetup scheduler for real-world encounters !
+module opportunity_web
+
+import opportunity_controller
+import opts
+import privileges
+
+# Avoids a crash when running automatic tests
+if "NIT_TESTING".environ == "true" then exit 0
+
+var iface = "localhost:80"
+
+var vh = new VirtualHost(iface)
+vh.routes.add new Route("/rest/", new OpportunityRESTAction)
+vh.routes.add new Route(null, new OpportunityWelcome)
+
+var fac = new HttpFactory.and_libevent
+fac.config.virtual_hosts.add vh
+
+print "Launching server on http://{iface}/"
+fac.run
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+
+# Contains the main components of a webpage for Opportunity
+module boilerplate
+
+import template
+
+# Header for a Opportunity page
+class OpportunityHeader
+ super Template
+
+ # Javascript code that is included in the `OpportunityPage`
+ var page_js = "" is writable
+
+ redef fun rendering do
+ add """
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Opportunity - The meetup planner</title>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
+ <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
+ <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
+ <script>
+ {{{page_js}}}
+ </script>
+ <style>
+ .menu {
+ background-color: #0d8921;
+ background-repeat: repeat-x;
+ color: white;
+ }
+ .navbar-brand{
+ color: white;
+ }
+ .navbar-brand:hover{
+ color: #EEEEEE;
+ }
+ .navbar-nav{
+ color: white;
+ }
+ .navbar-nav: hover{
+ background-color: #0d8921;
+ color: white;
+ }
+ .answer:hover {
+ cursor:pointer;
+ background-color:#0d8921;
+ color:white;
+ }
+ .opportunity-action:hover {
+ cursor:pointer;
+ }
+ </style>
+</head>
+<body>
+<nav class="menu" role="navigation">
+ <div class="container">
+ <div class="navbar-header">
+ <a class="navbar-brand" href="/" >Opportunity</a>
+ </div>
+ </div>
+</nav>
+<div class="container-fluid">
+"""
+ end
+
+end
+
+# Footer for a Opportunity page
+class OpportunityFooter
+ super Template
+
+ redef fun rendering do
+ add """
+</div>
+</body>
+<div class="footer">
+ <div class="well well-sm">
+ <p class="text-muted text-center">
+ Opportunity, the meetup planner.
+ </p>
+ <p class="text-muted text-center">
+ Proudly powered by <a href="http://nitlanguage.org/">Nit</a>!
+ </p>
+ </div>
+</div>
+</html>
+"""
+ end
+
+end
+
+# Any Opportunity page that contains the header, body and footer.
+class OpportunityPage
+ super Template
+
+ var header = new OpportunityHeader
+
+ var body: Streamable = "" is writable
+
+ var footer = new OpportunityFooter
+
+ redef fun rendering do
+ add header
+ add body
+ add footer
+ end
+
+end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+
+# Shows a meetup and allows to modify its participants
+module meetup
+
+import opportunity_model
+import boilerplate
+import welcome
+import template
+
+# Shows a meetup and allows to modify its participants
+class OpportunityMeetupPage
+ super OpportunityPage
+
+ # Meetup the page is supposed to show
+ var meetup: nullable Meetup = null
+
+ init from_id(id: String) do
+ var db = new OpportunityDB.open("opportunity")
+ meetup = db.find_meetup_by_id(id)
+ db.close
+ end
+
+ init do
+ header.page_js = """
+ function change_answer(ele){
+ var e = document.getElementById(ele.id);
+ var i = e.innerHTML;
+ var ans = true;
+ if(i === "<center>✔</center>"){
+ ans = false;
+ e.innerHTML = "<center>✘</center>"
+ e.style.color = "red";
+ }else{
+ e.innerHTML = "<center>✔</center>";
+ e.style.color = "green";
+ }
+ var a = ele.id.split('_')
+ var pid = a[1]
+ var aid = a[2]
+ $.ajax({
+ type: "POST",
+ url: "/rest/answer",
+ data: {
+ answer_id: aid,
+ pers_id: pid,
+ answer: ans
+ }
+ });
+ }
+ function change_temp_answer(ele){
+ var e = document.getElementById(ele.id);
+ var i = e.innerHTML;
+ var ans = true;
+ if(i === "<center>✔</center>"){
+ ans = false;
+ e.innerHTML = "<center>✘</center>";
+ e.style.color = "red";
+ }else{
+ e.innerHTML = "<center>✔</center>";
+ e.style.color = "green";
+ }
+ }
+ function add_part(ele){
+ var e = document.getElementById(ele.id);
+ var pname = document.getElementById("new_name").value;
+ var arr = e.id.split("_");
+ var mid = arr[1];
+ var ans = $('#' + ele.id).parent().parent().parent().children(".answer");
+ ansmap = {};
+ for(i=0;i<ans.length;i++){
+ var curr = ans.eq(i)
+ if(curr[0].innerHTML === "✔"){
+ ansmap[curr.attr('id')] = true
+ }else{
+ ansmap[curr.attr('id')] = false
+ }
+ }
+ $.ajax({
+ type: "POST",
+ url: "/rest/meetup/new_pers",
+ data: {
+ meetup_id: mid,
+ persname: pname,
+ answers: $.param(ansmap)
+ }
+ })
+ .done(function(data){
+ location.reload();
+ })
+ .fail(function(data){
+ //TODO: Notify of failure
+ });
+ }
+ function remove_people(ele){
+ var arr = ele.id.split("_")
+ var pid = arr[1]
+ $('#' + ele.id).parent().remove();
+ $.ajax({
+ type: "POST",
+ url: "/rest/people",
+ data: {
+ method: "DELETE",
+ p_id: pid
+ }
+ });
+ }
+ """
+ end
+
+
+ redef fun rendering do
+ if meetup == null then
+ add((new OpportunityHomePage).write_to_string)
+ return
+ end
+ add header
+ var db = new OpportunityDB.open("opportunity")
+ add meetup.to_html(db)
+ db.close
+ add footer
+ end
+end
+
+redef class Meetup
+ # Build the HTML for `self`
+ fun to_html(db: OpportunityDB): Streamable do
+ var t = new Template
+ t.add """
+<div class="page-header">
+ <center><h1>{{{name}}}</h1></center>
+ <center><h4>When : {{{date}}}</h4></center>
+ <center><h4>Where : {{{place}}}</h4></center>
+</div>
+<table class="table">
+"""
+ t.add "<th></th>"
+ t.add "<th>Participating</th>"
+ for i in answers(db) do
+ t.add "<th>"
+ t.add i.to_s
+ t.add "</th>"
+ end
+ t.add "</tr>"
+ for i in participants(db) do
+ t.add "<tr>"
+ t.add """<td class="opportunity-action" style="color: red;" onclick="remove_people(this)" id="remove_{{{i.id}}}"><center>❌</center></td>"""
+ i.load_answers(db, self)
+ t.add "<td>"
+ t.add i.to_s
+ t.add "</td>"
+ for j,k in i.answers do
+ t.add """<td class="answer" onclick="change_answer(this)" id="answer_{{{j.id}}}_{{{i.id}}}""""
+ if k then
+ t.add " style=\"color:green;\""
+ else
+ t.add " style=\"color:red;\""
+ end
+ t.add"><center>"
+ if k then
+ t.add "✔"
+ else
+ t.add "✘"
+ end
+ t.add "</center></td>"
+ end
+ t.add "</tr>"
+ end
+ t.add """
+<tr id="newrow">
+<td><center><span id="add_{{{id}}}" onclick="add_part(this)" style="color:green;" class="action"><strong>+</strong></span></center></td>
+ <td><input id="new_name" type="text" placeholder="Your name" class="input-large"></td>
+ """
+ for i in answers(db) do
+ t.add "<td class=\"answer\" id=\"newans_{i.id}\" onclick=\"change_temp_answer(this)\" style=\"color:red;\"><center>✘</center></td>"
+ end
+ t.add "</tr>"
+ t.add "</table>"
+ return t
+ end
+end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+
+# Page to show when the creation of a Meetup is successful
+module meetup_confirmation
+
+import boilerplate
+import opportunity_model
+
+# Show this page when a `Meetup` is sucessfully created.
+class MeetupConfirmation
+ super OpportunityPage
+
+ var meetup: Meetup
+
+ init do
+ body = """
+ <div class="page-header">
+ <center><h1>Congratulations !</h1></center>
+ </div>
+ <p class="text-center">
+ <h2> Your meetup was successfully created. </h2>
+ <p>
+ You can invite people to participate to your event by sharing them this link : <a href="/?meetup_id={{{meetup.id}}}">{{{meetup.name}}}</a>
+ </p>
+ <p>
+ See you soon for more Opportunities !
+ </p>
+ </p>
+ """
+ end
+
+end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+
+module meetup_creation
+
+import boilerplate
+
+class MeetupCreationPage
+ super OpportunityPage
+
+ init do
+ header.page_js = """
+ function new_answer(sender){
+ var ansdiv = $('#answers');
+ var nb = ansdiv.children()
+ var s = nb.last();
+ var ss = s.attr("id").split('_');
+ var l = ss[ss.length - 1];
+ nb = parseInt(l) + 1;
+ var ch = ansdiv.children();
+ ch.last().after('<input name="answer_' + nb + '" id="answer_' + nb + '" class="form-control" type="text" placeholder="Answer">')
+ ch.last().after('<label for="answer_' + nb + '">' + nb + '</label>');
+ }
+ """
+ body = """
+ <div class="page-header">
+ <center><h1>New meetup</h1></center>
+ </div>
+ <center>
+ <form action="meetup_create" method="POST" role="form">
+ <div class = "form-group">
+ <label for="meetup_name">Meetup Name : </label>
+ <input name="meetup_name" id="meetup_name" type="text" class="form-control" placeholder="Meetup Name"/>
+ <label for="meetup_date">When ? </label>
+ <input name="meetup_date" id="meetup_date" type="text" class="form-control" placeholder="Time of the event">
+ <label for="meetup=place">Where ? </label>
+ <input name="meetup_place" id="meetup_place" type="text" class="form-control" placeholder="Place of the event">
+ </div>
+ <div id="answers" class="form-group">
+ <h2>Answers</h2>
+ <label for="answer_1">1</label>
+ <input name="answer_1" id="answer_1" type="text" class="form-control" placeholder="Answer">
+ </div>
+ <div class="form-group">
+ <button type="button" class="btn btn-lg" onclick="new_answer(this)">Add answer</button>
+ </div>
+ <div class="form-group">
+ <button type="submit" class="btn btn-lg btn-success">Finish</button>
+ </div>
+ </form>
+ </center>
+"""
+ end
+
+end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+
+# All the templates used for the view are to be declared here
+module templates
+
+import boilerplate
+import welcome
+import meetup_creation
+import meetup
+import meetup_confirmation
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License
+
+# Welcome page for Opportunity
+module welcome
+
+import boilerplate
+
+# Welcome page for Opportunity
+class OpportunityHomePage
+ super OpportunityPage
+
+ init do
+ body = """
+ <div class="page-header">
+ <h1 class="text-center">Welcome to opportunity !</h1>
+ </div>
+ <p class="text-center">
+ <p class="text-center">Opportunity is a free (as in free software), easy-to-use, meetup planifier.</p>
+ <p class="text-center">You can start using it right now by creating a new Meetup and sharing it with your friends!</p>
+ <p class="text-center">
+ <form action="new_meetup">
+ <button type="submit" class="btn btn-lg center-block btn-success">Create a Meetup</button>
+ </form>
+ </p>
+ </p>
+"""
+ end
+
+end