| rev | line source | 
| bsw@286 | 1 Event = mondelefant.new_class() | 
| bsw@286 | 2 Event.table = 'event' | 
| bsw@286 | 3 | 
| bsw@286 | 4 Event:add_reference{ | 
| bsw@286 | 5   mode          = 'm1', | 
| bsw/jbe@1309 | 6   to            = "Unit", | 
| bsw/jbe@1309 | 7   this_key      = 'unit_id', | 
| bsw/jbe@1309 | 8   that_key      = 'id', | 
| bsw/jbe@1309 | 9   ref           = 'unit', | 
| bsw/jbe@1309 | 10 } | 
| bsw/jbe@1309 | 11 | 
| bsw/jbe@1309 | 12 Event:add_reference{ | 
| bsw/jbe@1309 | 13   mode          = 'm1', | 
| bsw/jbe@1309 | 14   to            = "Area", | 
| bsw/jbe@1309 | 15   this_key      = 'area_id', | 
| bsw/jbe@1309 | 16   that_key      = 'id', | 
| bsw/jbe@1309 | 17   ref           = 'area', | 
| bsw/jbe@1309 | 18 } | 
| bsw/jbe@1309 | 19 | 
| bsw/jbe@1309 | 20 Event:add_reference{ | 
| bsw/jbe@1309 | 21   mode          = 'm1', | 
| bsw/jbe@1309 | 22   to            = "Policy", | 
| bsw/jbe@1309 | 23   this_key      = 'policy_id', | 
| bsw/jbe@1309 | 24   that_key      = 'id', | 
| bsw/jbe@1309 | 25   ref           = 'policy', | 
| bsw/jbe@1309 | 26 } | 
| bsw/jbe@1309 | 27 | 
| bsw/jbe@1309 | 28 Event:add_reference{ | 
| bsw/jbe@1309 | 29   mode          = 'm1', | 
| bsw@286 | 30   to            = "Issue", | 
| bsw@286 | 31   this_key      = 'issue_id', | 
| bsw@286 | 32   that_key      = 'id', | 
| bsw@286 | 33   ref           = 'issue', | 
| bsw@287 | 34 } | 
| bsw@287 | 35 | 
| bsw@414 | 36 Event:add_reference{ | 
| bsw@414 | 37   mode          = 'm1', | 
| bsw@712 | 38   to            = "Initiative", | 
| bsw@712 | 39   this_key      = 'initiative_id', | 
| bsw@712 | 40   that_key      = 'id', | 
| bsw@712 | 41   ref           = 'initiative', | 
| bsw@712 | 42 } | 
| bsw@712 | 43 | 
| bsw@712 | 44 Event:add_reference{ | 
| bsw@712 | 45   mode          = 'm1', | 
| bsw@414 | 46   to            = "Suggestion", | 
| bsw@414 | 47   this_key      = 'suggestion_id', | 
| bsw@414 | 48   that_key      = 'id', | 
| bsw@414 | 49   ref           = 'suggestion', | 
| bsw@414 | 50 } | 
| bsw@414 | 51 | 
| bsw@414 | 52 Event:add_reference{ | 
| bsw@414 | 53   mode          = 'm1', | 
| bsw@414 | 54   to            = "Member", | 
| bsw@414 | 55   this_key      = 'member_id', | 
| bsw@414 | 56   that_key      = 'id', | 
| bsw@414 | 57   ref           = 'member', | 
| bsw@414 | 58 } | 
| bsw@414 | 59 | 
| bsw/jbe@1309 | 60 function Event:by_member_id(member_id) | 
| bsw/jbe@1309 | 61   return Event:new_selector() | 
| bsw/jbe@1309 | 62     :add_where{ "member_id = ?", member_id } | 
| bsw/jbe@1309 | 63     :add_order_by("id DESC") | 
| bsw/jbe@1309 | 64     :exec() | 
| bsw/jbe@1309 | 65 end | 
| bsw/jbe@1309 | 66 | 
| bsw@405 | 67 function Event.object_get:event_name() | 
| bsw@405 | 68   return ({ | 
| bsw@405 | 69     issue_state_changed = _"Issue reached next phase", | 
| bsw@405 | 70     initiative_created_in_new_issue = _"New issue", | 
| bsw@414 | 71     initiative_created_in_existing_issue = _"New initiative", | 
| bsw@405 | 72     initiative_revoked = _"Initiative revoked", | 
| bsw@405 | 73     new_draft_created = _"New initiative draft", | 
| bsw@405 | 74     suggestion_created = _"New suggestion" | 
| bsw@405 | 75   })[self.event] | 
| bsw@405 | 76 end | 
| bsw@405 | 77 | 
| bsw@405 | 78 function Event.object_get:state_name() | 
| bsw@972 | 79   return Issue:get_state_name_for_state(self.state) | 
| bsw@405 | 80 end | 
| bsw@405 | 81 | 
| bsw@592 | 82 function Event.object:send_notification() | 
| bsw@592 | 83 | 
| bsw@363 | 84   local members_to_notify = Member:new_selector() | 
| bsw@1302 | 85     :join("member_to_notify", nil, "member_to_notify.id = member.id") | 
| bsw@1248 | 86     :join("event_for_notification", nil, { "event_for_notification.recipient_id = member.id AND event_for_notification.id = ?", self.id } ) | 
| bsw@726 | 87     -- SAFETY FIRST, NEVER send notifications for events more then 3 days in past or future | 
| bsw@1248 | 88     :add_where("now() - event_for_notification.occurrence BETWEEN '-3 days'::interval AND '3 days'::interval") | 
| bsw@866 | 89     -- do not notify a member about the events caused by the member | 
| bsw@1248 | 90     :add_where("event_for_notification.member_id ISNULL OR event_for_notification.member_id != member.id") | 
| bsw@1300 | 91     :add_where("member.notify_email NOTNULL") | 
| bsw@363 | 92     :exec() | 
| bsw@363 | 93 | 
| bsw@1159 | 94   io.stderr:write("Sending notifications for event " .. self.id .. " to " .. (#members_to_notify) .. " members\n") | 
| bsw@363 | 95 | 
| bsw@405 | 96   for i, member in ipairs(members_to_notify) do | 
| bsw@405 | 97     local subject | 
| bsw@405 | 98     local body = "" | 
| bsw@405 | 99 | 
| bsw@405 | 100     locale.do_with( | 
| bsw@1252 | 101       { lang = member.lang or config.default_lang }, | 
| bsw@405 | 102       function() | 
| bsw@1104 | 103 | 
| bsw@405 | 104         body = body .. _("[event mail]      Unit: #{name}", { name = self.issue.area.unit.name }) .. "\n" | 
| bsw@405 | 105         body = body .. _("[event mail]      Area: #{name}", { name = self.issue.area.name }) .. "\n" | 
| bsw@405 | 106         body = body .. _("[event mail]     Issue: ##{id}", { id = self.issue_id }) .. "\n\n" | 
| bsw@405 | 107         body = body .. _("[event mail]    Policy: #{policy}", { policy = self.issue.policy.name }) .. "\n\n" | 
| bsw@405 | 108         body = body .. _("[event mail]     Phase: #{phase}", { phase = self.state_name }) .. "\n\n" | 
| bsw@405 | 109 | 
| bsw@1252 | 110         local url | 
| bsw@1252 | 111 | 
| bsw@405 | 112         if self.initiative_id then | 
| bsw@405 | 113           url = request.get_absolute_baseurl() .. "initiative/show/" .. self.initiative_id .. ".html" | 
| bsw@405 | 114         else | 
| bsw@405 | 115           url = request.get_absolute_baseurl() .. "issue/show/" .. self.issue_id .. ".html" | 
| bsw@405 | 116         end | 
| bsw@405 | 117 | 
| bsw@405 | 118         body = body .. _("[event mail]       URL: #{url}", { url = url }) .. "\n\n" | 
| bsw@405 | 119 | 
| bsw@1248 | 120         local leading_initiative | 
| bsw@1248 | 121 | 
| bsw@405 | 122         if self.initiative_id then | 
| bsw@405 | 123           local initiative = Initiative:by_id(self.initiative_id) | 
| bsw@405 | 124           body = body .. _("i#{id}: #{name}", { id = initiative.id, name = initiative.name }) .. "\n\n" | 
| bsw@405 | 125         else | 
| bsw@405 | 126           local initiative_count = Initiative:new_selector() | 
| bsw@405 | 127             :add_where{ "initiative.issue_id = ?", self.issue_id } | 
| bsw@405 | 128             :count() | 
| bsw@405 | 129           local initiatives = Initiative:new_selector() | 
| bsw@405 | 130             :add_where{ "initiative.issue_id = ?", self.issue_id } | 
| bsw@1252 | 131             :add_order_by("initiative.admitted DESC NULLS LAST, initiative.rank NULLS LAST, initiative.harmonic_weight DESC NULLS LAST, id") | 
| bsw@405 | 132             :limit(3) | 
| bsw@405 | 133             :exec() | 
| bsw@405 | 134           for i, initiative in ipairs(initiatives) do | 
| bsw@1248 | 135             if i == 1 then | 
| bsw@1248 | 136               leading_initiative = initiative | 
| bsw@1248 | 137             end | 
| bsw@405 | 138             body = body .. _("i#{id}: #{name}", { id = initiative.id, name = initiative.name }) .. "\n" | 
| bsw@405 | 139           end | 
| bsw@405 | 140           if initiative_count - 3 > 0 then | 
| bsw@764 | 141             body = body .. _("and #{count} more initiatives", { count = initiative_count - 3 }) .. "\n" | 
| bsw@405 | 142           end | 
| bsw@405 | 143           body = body .. "\n" | 
| bsw@405 | 144         end | 
| bsw@405 | 145 | 
| bsw@1248 | 146         subject = config.mail_subject_prefix | 
| bsw@1248 | 147 | 
| bsw@1248 | 148         if self.event == "issue_state_changed" then | 
| bsw@1252 | 149           subject = subject .. _("State of #{issue} changed to #{state} / #{initiative}", { issue = self.issue.name, state = Issue:get_state_name_for_state(self.state), initiative = leading_initiative.display_name }) | 
| bsw@1248 | 150         elseif self.event == "initiative_revoked" then | 
| bsw@1248 | 151           subject = subject .. _("Initiative revoked: #{initiative_name}", { initiative_name = self.initiative.display_name }) | 
| bsw@405 | 152         end | 
| bsw@1248 | 153 | 
| bsw@405 | 154         local success = net.send_mail{ | 
| bsw@405 | 155           envelope_from = config.mail_envelope_from, | 
| bsw@405 | 156           from          = config.mail_from, | 
| bsw@405 | 157           reply_to      = config.mail_reply_to, | 
| bsw@1305 | 158           to            = { name = member.name, address = member.notify_email }, | 
| bsw@406 | 159           subject       = subject, | 
| bsw@405 | 160           content_type  = "text/plain; charset=UTF-8", | 
| bsw@405 | 161           content       = body | 
| bsw@405 | 162         } | 
| bsw@405 | 163 | 
| bsw@405 | 164       end | 
| bsw@405 | 165     ) | 
| bsw@287 | 166   end | 
| bsw@287 | 167 | 
| bsw@287 | 168 end | 
| bsw@287 | 169 | 
| bsw/jbe@1309 | 170 function Event:get_events_after_id(id) | 
| bsw/jbe@1309 | 171   return ( | 
| bsw/jbe@1309 | 172     Event:new_selector() | 
| bsw/jbe@1309 | 173       :add_where{ "event.id > ?", id } | 
| bsw/jbe@1309 | 174       :add_order_by("event.id") | 
| bsw/jbe@1309 | 175       :exec() | 
| bsw/jbe@1309 | 176   ) | 
| bsw/jbe@1309 | 177 end | 
| bsw/jbe@1309 | 178 | 
| bsw/jbe@1309 | 179 | 
| bsw/jbe@1309 | 180 | 
| bsw/jbe@1309 | 181 Event.handlers = {} | 
| bsw/jbe@1309 | 182 | 
| bsw/jbe@1309 | 183 function Event:add_handler(func) | 
| bsw/jbe@1309 | 184   table.insert(Event.handlers, func) | 
| bsw/jbe@1309 | 185 end | 
| bsw/jbe@1309 | 186 | 
| bsw/jbe@1309 | 187 function Event:process_stream(poll) | 
| bsw/jbe@1309 | 188 | 
| bsw/jbe@1309 | 189   db:query('LISTEN "event"') | 
| bsw/jbe@1309 | 190 | 
| bsw/jbe@1309 | 191   local last_event_id = EventProcessed:get_last_id() | 
| bsw@363 | 192 | 
| bsw/jbe@1309 | 193   while true do | 
| bsw/jbe@1309 | 194 | 
| bsw/jbe@1309 | 195     local events = Event:get_events_after_id(last_event_id) | 
| bsw/jbe@1309 | 196 | 
| bsw/jbe@1309 | 197     for i_event, event in ipairs(events) do | 
| bsw/jbe@1309 | 198       last_event_id = event.id | 
| bsw/jbe@1309 | 199       EventProcessed:set_last_id(last_event_id) | 
| bsw/jbe@1309 | 200       for i_handler, event_handler in ipairs(Event.handlers) do | 
| bsw/jbe@1309 | 201         event_handler(event) | 
| bsw/jbe@1309 | 202       end | 
| bsw/jbe@1309 | 203       event:send_notification() | 
| bsw/jbe@1309 | 204     end | 
| bsw@287 | 205 | 
| bsw/jbe@1309 | 206     if poll then | 
| bsw/jbe@1309 | 207       while not db:wait(0) do | 
| bsw/jbe@1309 | 208         if not poll({[db.fd]=true}, nil, nil, true) then | 
| bsw/jbe@1309 | 209           goto exit | 
| bsw/jbe@1309 | 210         end | 
| bsw/jbe@1309 | 211       end | 
| bsw@363 | 212     else | 
| bsw/jbe@1309 | 213       db:wait() | 
| bsw@363 | 214     end | 
| bsw/jbe@1309 | 215     while db:wait(0) do end  -- discard multiple events | 
| bsw@364 | 216 | 
| bsw@287 | 217   end | 
| bsw@287 | 218 | 
| bsw/jbe@1309 | 219   ::exit:: | 
| bsw/jbe@1309 | 220 | 
| bsw/jbe@1309 | 221   db:query('UNLISTEN "event"') | 
| bsw/jbe@1309 | 222 | 
| bsw@364 | 223 end |