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