This is an automated email from the ASF dual-hosted git repository.

sebb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/whimsy.git


The following commit(s) were added to refs/heads/master by this push:
     new c84b906f Handle withdrawal requests
c84b906f is described below

commit c84b906f6a4094ac6e2aea6d3e91a7d641c66f66
Author: Sebb <[email protected]>
AuthorDate: Mon Sep 16 21:53:54 2024 +0100

    Handle withdrawal requests
---
 lib/whimsy/asf/subreq.rb                 | 103 ++++++++++++++++++++++++++
 www/roster/views/actions/memstat.json.rb | 119 +++++++++++++++++++------------
 www/roster/views/person/memstat.js.rb    |   7 +-
 3 files changed, 178 insertions(+), 51 deletions(-)

diff --git a/lib/whimsy/asf/subreq.rb b/lib/whimsy/asf/subreq.rb
new file mode 100644
index 00000000..2c5a8612
--- /dev/null
+++ b/lib/whimsy/asf/subreq.rb
@@ -0,0 +1,103 @@
+#!/usr/bin/env ruby
+$LOAD_PATH.unshift '/srv/whimsy/lib'
+
+require 'wunderbar'
+require 'whimsy/asf/config'
+require 'whimsy/asf/svn'
+
+module ASF
+  # Handle subscribe and unsubscribe requests
+  #
+  # This is a two part process (so the request can be shown to the user before 
committing  if required)
+  # user = ASF::Person.new(person)
+  # request = ASF::Subreq.create_request(user, email, list)
+  # ASF::Subreq.queue_request(reqtype, request, wunderbar, options)
+  module Subreq
+    FORMAT_NUMBER = 3 # json format number
+
+    # Create the request hash
+    # Params:
+    # user: ASF::Person instance
+    # email: the email to unsubscribe
+    # list: listname@domain
+    # Returns: hash suitable for saving as a JSON file
+    def self.create_request(user, email, list)
+      {
+        version: FORMAT_NUMBER,
+        availid: user.id,
+        addr: email,
+        listkey: list,
+        member_p: user.asf_member?, # does not appear to be used
+        chair_p: ASF.pmc_chairs.include?(user), # does not appear to be used
+      }
+    end
+
+    # Queue the update request
+    # Params:
+    # - reqtype: 'sub'|'unsub'
+    # - request: hash generated by create_request
+    # - wbar: wunderbar context (or nil)
+    # - credentials: has containing credentials; may also contain options, 
e.g. verbose: true
+    def self.queue_request(reqtype, request, wbar, credentials)
+      raise Exception.new("Unexpected reqtype: #{reqtype}") unless %w{sub 
unsub}.include? reqtype
+
+      queue_url = ASF::SVN.svnpath!('subreq') # only this URL is defined
+      queue_url.sub! '/subreq', '/unsubreq' if reqtype == 'unsub'
+
+      # subreq/unsubreq now accept name@dom
+      # Keep the key for the file name
+      list = request[:listkey]
+      listkey = ASF::Mail.listdom2listkey(list)
+      userid = request[:availid]
+
+      content = JSON.pretty_generate(request) + "\n"
+
+      # Each user can only (un)subscribe once to each list in each timeslot
+      fn = "#{userid}-#{listkey}.json"
+
+      Dir.mktmpdir do |tmpdir|
+
+        if wbar
+          ASF::SVN.svn_!('checkout', [queue_url, tmpdir], wbar, credentials)
+        else
+          ASF::SVN.svn!('checkout', [queue_url, tmpdir], credentials)
+        end
+        path = File.join(tmpdir, fn)
+        if File.exist? path
+          File.write(path, content) # overwrite
+        else
+          File.write(path, content) # new
+          ASF::SVN.svn('add', path)
+        end
+
+        if reqtype == 'unsub'
+          message = "#{list} -= #{userid}"
+        else
+          message = "#{list} += #{userid}"
+        end
+
+        options = credentials.merge({msg: message})
+        if wbar
+          ASF::SVN.svn_!('commit', path, wbar, options)
+        else
+          ASF::SVN.svn!('commit', path, options)
+        end
+      end
+      return nil
+    end
+  end
+end
+
+if __FILE__ == $0
+  list = ARGV.shift or raise Exception.new('need list')
+  person = ARGV.shift or raise Exception.new('need id')
+  email = ARGV.shift or raise Exception.new('need email')
+  dryrun = ARGV.shift != 'nodryrun'
+  require 'whimsy/asf'
+  user = ASF::Person.new(person)
+  request = ASF::Subreq.create_request(user, email, list)
+  puts request
+  rc,err = ASF::Subreq.queue_request('unsub', request, nil, {dryrun: dryrun, 
verbose: true})
+  puts rc
+  p err
+end
diff --git a/www/roster/views/actions/memstat.json.rb 
b/www/roster/views/actions/memstat.json.rb
index 84902fd8..caa42621 100644
--- a/www/roster/views/actions/memstat.json.rb
+++ b/www/roster/views/actions/memstat.json.rb
@@ -1,5 +1,6 @@
 # get entry for @userid
 require 'wunderbar'
+
 user = ASF::Person.find(@userid)
 entry = user.members_txt(true)
 raise Exception.new("Unable to find member entry for #{@userid}") unless entry
@@ -56,55 +57,79 @@ if @action == 'emeritus' or @action == 'active' or @action 
== 'deceased'
     [ASF::Member.normalize(text), extra]
   end
 
-  ############### INCOMPLETE #######################
-# elsif @action == 'withdraw' # process withdrawal request (secretary only)
-#   # TODO 
-#   # update LDAP - remove from members
-#   # unsubscribe from member only mailing lists:
-#   # - compare subscriptions against ASF::Mail.cansub
-#   Tempfile.create('withdraw') do |tempfile|
-#     ASF::SVN.multiUpdate_ members_txt, message, env, _ do |text|
-#       extra = []
-#       # remove user's entry
-#       unless text.sub! entry, '' # e.g. if the workspace was out of date
-#         raise Exception.new('Failed to remove existing entry -- try 
refreshing')
-#       end
-#       # save the entry to the archive
-#       File.write(tempfile, entry)
-#       tempfile.close
-#       extra << ['put' , tempfile.path, ASF::SVN.svnpath!('withdrawn', 
'archive', "#{@userid}.txt")]
-
-#       # Find matching request for the id
-#       pathname, basename = ASF::WithdrawalRequestFiles.findpath(@userid, env)
-#       unless pathname
-#         raise Exception.new("Failed to find withdrawal pending file for 
#{@userid}")
-#       end
-
-#       # Move the request from pending - this should also work for directories
-#       extra << ['mv', pathname, ASF::SVN.svnpath!('withdrawn', basename)]
-#       [ASF::Member.normalize(text), extra]
-#     end
-#     ASF::WithdrawalRequestFiles.refreshnames(true, env) # update the listing 
if successful
-#     ASF::Mail.configure
-#     mail = Mail.new do
-#       from '[email protected]'
-#       to "#{USERNAME}<#{USERMAIL}>"
-#       subject "Acknowledgement of membership withdrawal from #{USERNAME}"
-#       text_part do
-#         body <<~EOD
-#         The membership withdrawal request that was registered for you has 
now been actioned.
-#         Your details have been removed from the membership roster.
-#         You have also been unsubscribed from members-only private email 
lists.
+elsif @action == 'withdraw' # process withdrawal request (secretary only)
+  require 'whimsy/asf/subreq'
+
+  # TODO 
+  # Check members.md for entry and report - how?
+
+  # unsubscribe from member only mailing lists:
+  all_mail = user.all_mail # their known emails
+  pmc_chair = ASF.pmc_chairs.include?(user)
+  # to which (P)PMCs do they belong?
+  ldap_pmcs = user.committees.map(&:mail_list)
+  ldap_pmcs += user.podlings.map(&:mail_list)
+  # What are their subscriptions?
+  subs = ASF::MLIST.subscriptions(all_mail)[:subscriptions]
+  subs += ASF::MLIST.digests(all_mail)[:digests]
+  # Check subscriptions for readability
+  subs.each do |list, email|
+    unless ASF::Mail.canread(list, false, pmc_chair, ldap_pmcs)
+      request = ASF::Subreq.create_request(user, email, list)
+      ASF::Subreq.queue_request('unsub', request, nil, {env: env})
+    end
+  end
+  # remove from LDAP member group
+  ASF::LDAP.bind(env.user, env.password) do
+    ASF::Group['member'].remove(user)
+  end
+
+  # Remove from members.txt
+  Tempfile.create('withdraw') do |tempfile|
+    ASF::SVN.multiUpdate_ members_txt, message, env, _, {verbose: true} do 
|text|
+      extra = []
+      # remove user's entry
+      unless text.sub! entry, '' # e.g. if the workspace was out of date
+        raise Exception.new('Failed to remove existing entry -- try 
refreshing')
+      end
+      # save the entry to the archive
+      File.write(tempfile, entry)
+      tempfile.close
+      extra << ['put' , tempfile.path, ASF::SVN.svnpath!('withdrawn', 
'archive', "#{@userid}.txt")]
+
+      # Find matching request for the id
+      pathname, basename = ASF::WithdrawalRequestFiles.findpath(@userid, env)
+      unless pathname
+        raise Exception.new("Failed to find withdrawal pending file for 
#{@userid}")
+      end
+
+      # Move the request from pending - this should also work for directories
+      extra << ['mv', pathname, ASF::SVN.svnpath!('withdrawn', basename)]
+      [ASF::Member.normalize(text), extra]
+    end
+    ASF::WithdrawalRequestFiles.refreshnames(true, env) # update the listing 
if successful
+
+    # Send confirmation email
+    ASF::Mail.configure
+    mail = Mail.new do
+      from '[email protected]'
+      to "#{USERNAME}<#{USERMAIL}>"
+      subject "Acknowledgement of membership withdrawal from #{USERNAME}"
+      text_part do
+        body <<~EOD
+        The membership withdrawal request that was registered for you has now 
been actioned.
+        Your details have been removed from the membership roster.
+        You have also been unsubscribed from members-only private email lists.
         
-#         Warm Regards,
+        Warm Regards,
         
-#         Secretary, Apache Software Foundation
-#         [email protected]
-#         EOD
-#       end
-#     end
-#     mail.deliver!
-#   end
+        Secretary, Apache Software Foundation
+        [email protected]
+        EOD
+      end
+    end
+    mail.deliver!
+  end
 
 elsif @action == 'rescind_withdrawal' # Secretary only
   pathname, _basename = ASF::WithdrawalRequestFiles.findpath(@userid, env)
diff --git a/www/roster/views/person/memstat.js.rb 
b/www/roster/views/person/memstat.js.rb
index 451a3e0c..aa141f42 100644
--- a/www/roster/views/person/memstat.js.rb
+++ b/www/roster/views/person/memstat.js.rb
@@ -59,11 +59,10 @@ class PersonMemberStatus < Vue
                     name: 'action', value: 'emeritus'
                 end
                 if committer.forms['withdrawal_request']
-                  # INCOMPLETE
-                  # _button.btn.btn_primary 'move to withdrawn',
-                  # name: 'action', value: 'withdraw'
+                  _button.btn.btn_primary 'process withdrawal',
+                    name: 'action', value: 'withdraw'
                   _button.btn.btn_primary 'rescind withdrawal request',
-                  name: 'action', value: 'rescind_withdrawal'
+                    name: 'action', value: 'rescind_withdrawal'
                 end
               end
             end # end _form

Reply via email to