Commit c7ee67b1 authored by Frank Bergmann's avatar Frank Bergmann

Initial Import

parents
Pipeline #86 failed with stages
<?xml version="1.0"?>
<!-- Generated by the OpenACS Package Manager -->
<package key="acs-messaging" url="http://openacs.org/repository/apm/packages/acs-messaging/" type="apm_service">
<package-name>Messaging</package-name>
<pretty-plural>Messaging Services</pretty-plural>
<initial-install-p>t</initial-install-p>
<singleton-p>t</singleton-p>
<version name="5.1.5" url="http://openacs.org/repository/download/apm/acs-messaging-5.1.5.apm">
<owner url="mailto:akk+@cs.cmu.edu">Anukul Kapoor</owner>
<owner url="mailto:prevost@maya.com">John Prevost</owner>
<owner url="mailto:vinod@kurup.com">Vinod Kurup</owner>
<summary>General messaging for bboard and general comments.</summary>
<release-date>2004-02-28</release-date>
<maturity>3</maturity>
<vendor url="http://openacs.org">OpenACS</vendor>
<description format="text/html">Provides generic message services, with email sending. acs-mail-lite and notifications are the
prefered packages for delivering this functionality and it is anticipated that this package will ultimately be deprecated.</description>
<provides url="acs-messaging" version="5.1.4"/>
<requires url="acs-content-repository" version="5.0.0"/>
<requires url="acs-kernel" version="5.0.0"/>
<callbacks>
</callbacks>
<parameters>
<!-- No version parameters -->
</parameters>
</version>
</package>
--
-- packages/acs-messaging/sql/acs-messaging-create.sql
--
-- @author John Prevost <jmp@arsdigita.com>
-- @creation-date 2000-08-27
-- @cvs-id $Id$
--
set feedback off
-- Object System Metadata ----------------------------------------------
begin
acs_object_type.create_type (
supertype => 'content_item',
object_type => 'acs_message',
pretty_name => 'Message',
pretty_plural => 'Messages',
table_name => 'ACS_MESSAGES',
id_column => 'MESSAGE_ID',
name_method => 'ACS_MESSAGE.NAME'
);
acs_object_type.create_type (
supertype => 'content_revision',
object_type => 'acs_message_revision',
pretty_name => 'Message Revision',
pretty_plural => 'Message Revisions',
table_name => 'CR_REVISIONS',
id_column => 'REVISION_ID',
name_method => 'ACS_OBJECT.DEFAULT_NAME'
);
end;
/
show errors
-- Raw Tables and Comments ---------------------------------------------
create table acs_messages ( -- extends cr_items
message_id integer
constraint acs_messages_message_id_fk
references cr_items (item_id) on delete cascade
constraint acs_messages_pk
primary key,
-- we will need to find a way to make reply_to go to 0 instead of null
-- to improve scalability
reply_to integer
constraint acs_messages_reply_to_fk
references acs_messages (message_id) on delete set null,
sent_date date
constraint acs_messages_sent_date_nn
not null,
sender integer
constraint acs_messages_sender_fk
references parties (party_id),
rfc822_id varchar2(250)
constraint acs_messages_rfc822_id_nn
not null
constraint acs_messages_rfc822_id_un
unique
);
create index acs_messages_reply_to_idx on acs_messages (reply_to);
create index acs_messages_sender_idx on acs_messages (sender);
comment on table acs_messages is '
A generic message which may be attached to any object in the system.
';
comment on column acs_messages.reply_to is '
Pointer to a message this message contains a reply to, for threading.
';
comment on column acs_messages.sent_date is '
The date the message was sent (may be distinct from when it was created
or published in the system.)
';
comment on column acs_messages.sender is '
The person who sent the message (may be distinct from the person who
entered the message in the system.)
';
comment on column acs_messages.rfc822_id is '
The RFC822 message-id of this message, for sending email.
';
create table acs_messages_outgoing (
message_id integer
constraint amo_message_id_fk
references acs_messages (message_id) on delete cascade,
to_address varchar2(1000)
constraint amo_to_address_nn
not null,
grouping_id integer,
wait_until date
constraint amo_wait_until_nn not null,
constraint acs_messages_outgoing_pk
primary key (message_id, to_address)
);
comment on table acs_messages_outgoing is '
Set of messages to be sent to parties. It is assumed that sending a
message either queues it in a real MTA or fails, so no information about
what''s been tried how many times is kept.
';
comment on column acs_messages_outgoing.to_address is '
The email address to send this message to. Note that this will
probably become a party_id again once upgrading a party to a user
is possible.
';
comment on column acs_messages_outgoing.grouping_id is '
This identifier is used to group sets of messages to be sent as
digests. When a message is about to be sent, any other messages
with the same grouping_id will be put together with it in a
digest. It is recommended but not required that an object id is
used. Bboard, for example, might use the forum id that the user''s
subscribed to. For instant (non-digest) updates, it would be
appropriate to use null, which is never equal to anything else.
';
comment on column acs_messages_outgoing.wait_until is '
Don''t schedule a send until after this date. If another message with
the same grouping ID is scheduled to be sent, then this message may be
sent at the same time. (So, for example, daily digests would be
achieved by setting the grouping_id to the same value, and the wait_until
value to the end of the current day. As soon as one message in the group
is to be sent, all will be sent.)
';
@@ acs-messaging-views
@@ acs-messaging-packages
set feedback on
--
-- packages/acs-messaging/sql/acs-messaging-drop.sql
--
-- @author akk@arsdigita.com
-- @creation-date 2000-08-31
-- @cvs-id $Id$
--
begin
acs_object_type.drop_type('acs_message');
end;
/
show errors
drop package acs_message;
drop table acs_messages_outgoing;
drop view acs_messages_all;
drop table acs_messages;
This diff is collapsed.
--
-- packages/acs-messaging/sql/acs-messaging-create.sql
--
-- @author John Prevost <jmp@arsdigita.com>
-- @creation-date 2000-11-15
-- @cvs-id $Id$
--
create or replace view acs_messages_all as
select m.message_id, m.reply_to, m.sent_date, m.sender, m.rfc822_id,
r.revision_id, r.title, r.mime_type, r.content
from cr_items i, cr_revisions r, acs_messages m
where i.item_id = m.message_id and r.revision_id = i.live_revision;
create or replace view acs_messages_latest as
select m.message_id, m.reply_to, m.sent_date, m.sender, m.rfc822_id,
r.revision_id, r.title, r.mime_type, r.content
from cr_items i, cr_revisions r, acs_messages m
where i.item_id = m.message_id
and r.revision_id = content_item.get_latest_revision(i.item_id);
--
-- acs-messaging sql/upgrade-4.0-4.0.1a.sql
--
-- @author jmp@arsdigita.com
-- @creation-date 2000-11-03
-- @cvs-id $Id$
--
alter table acs_messages add (
sent_date date
constraint acs_messages_sent_date_nn
not null
disable,
sender integer
constraint acs_messages_sender_fk
references parties (party_id)
disable,
rfc822_id varchar2(250)
constraint acs_messages_rfc822_id_nn
not null
disable
constraint acs_messages_rfc822_id_un
unique
disable
);
create table acs_mess_up (
id integer primary key,
sent_date date,
sender integer,
rfc822_id varchar2(250)
);
insert into acs_mess_up
select m.message_id,
r.publish_date as sent_date,
o.creation_user as sender,
(sysdate || '.' || message_id || '@'
|| utl_inaddr.get_host_name||'.hate') as rfc822_id
from acs_objects o, cr_items i, cr_revisions r, acs_messages m
where m.message_id = i.item_id
and m.message_id = o.object_id
and r.revision_id = i.live_revision;
update acs_messages
set sent_date = (select sent_date from acs_mess_up where id = message_id),
sender = (select sender from acs_mess_up where id = message_id),
rfc822_id = (select rfc822_id from acs_mess_up where id = message_id);
drop table acs_mess_up;
alter table acs_messages modify constraint acs_messages_sent_date_nn enable;
alter table acs_messages modify constraint acs_messages_sender_fk enable;
alter table acs_messages modify constraint acs_messages_rfc822_id_nn enable;
alter table acs_messages modify constraint acs_messages_rfc822_id_un enable;
create or replace view acs_messages_all as
select m.message_id, m.reply_to, m.sent_date, m.sender, m.rfc822_id,
r.title, r.mime_type, r.content, o.context_id
from acs_objects o, cr_items i, cr_revisions r, acs_messages m
where o.object_id = m.message_id and i.item_id = m.message_id
and r.revision_id = i.live_revision;
create table acs_messages_outgoing (
message_id integer
constraint amo_message_id_fk
references acs_messages (message_id) on delete cascade,
recipient_id integer
constraint amo_recipient_id_fk
references parties (party_id),
grouping_id integer,
wait_until date not null,
constraint acs_messages_outgoing_pk
primary key (message_id, recipient_id)
);
comment on column acs_messages_outgoing.grouping_id is '
This identifier is used to group sets of messages to be sent as
digests. When a message is about to be sent, any other messages
with the same grouping_id will be put together with it in a
digest. It is recommended but not required that an object id is
used. Bboard, for example, might use the forum id that the user''s
subscribed to. For instant (non-digest) updates, it would be
appropriate to use null, which is never equal to anything else.
';
comment on column acs_messages_outgoing.wait_until is '
Don''t schedule a send until after this date. If another message with
the same grouping ID is scheduled to be sent, then this message may be
sent at the same time. (So, for example, daily digests would be
achieved by setting the grouping_id to the same value, and the wait_until
value to the end of the current day. As soon as one message in the group
is to be sent, all will be sent.)
';
create or replace package acs_message
as
function new (
message_id in acs_messages.message_id%TYPE default null,
reply_to in acs_messages.reply_to%TYPE default null,
sent_date in acs_messages.sent_date%TYPE default sysdate,
sender in acs_messages.sender%TYPE default null,
rfc822_id in acs_messages.rfc822_id%TYPE default null,
title in cr_revisions.title%TYPE default null,
mime_type in cr_revisions.mime_type%TYPE default 'text/plain',
text in varchar2 default null,
data in cr_revisions.content%TYPE default null,
context_id in acs_objects.context_id%TYPE,
creation_date in acs_objects.creation_date%TYPE default sysdate,
creation_user in acs_objects.creation_user%TYPE default null,
creation_ip in acs_objects.creation_ip%TYPE default null,
object_type in acs_objects.object_type%TYPE default 'acs_message'
) return acs_objects.object_id%TYPE;
procedure delete (
message_id in acs_messages.message_id%TYPE
);
function message_p (
message_id in acs_messages.message_id%TYPE
) return char;
procedure send (
message_id in acs_messages.message_id%TYPE,
recipient_id in parties.party_id%TYPE,
grouping_id in integer default NULL,
wait_until in date default SYSDATE
);
end acs_message;
/
show errors
create or replace package body acs_message
as
function new (
message_id in acs_messages.message_id%TYPE default null,
reply_to in acs_messages.reply_to%TYPE default null,
sent_date in acs_messages.sent_date%TYPE default sysdate,
sender in acs_messages.sender%TYPE default null,
rfc822_id in acs_messages.rfc822_id%TYPE default null,
title in cr_revisions.title%TYPE default null,
mime_type in cr_revisions.mime_type%TYPE default 'text/plain',
text in varchar2 default null,
data in cr_revisions.content%TYPE default null,
context_id in acs_objects.context_id%TYPE,
creation_date in acs_objects.creation_date%TYPE default sysdate,
creation_user in acs_objects.creation_user%TYPE default null,
creation_ip in acs_objects.creation_ip%TYPE default null,
object_type in acs_objects.object_type%TYPE default 'acs_message'
) return acs_objects.object_id%TYPE
is
v_message_id acs_messages.message_id%TYPE;
v_rfc822_id acs_messages.rfc822_id%TYPE;
v_name cr_items.name%TYPE;
begin
if message_id is null then
select acs_object_id_seq.nextval into v_message_id from dual;
else
v_message_id := message_id;
end if;
if rfc822_id is null then
v_rfc822_id := sysdate || '.' || v_message_id || '@' ||
utl_inaddr.get_host_name || '.hate';
else
v_rfc822_id := rfc822_id;
end if;
v_name := v_rfc822_id;
v_message_id := content_item.new (
name => v_name,
parent_id => context_id,
item_id => message_id,
creation_date => creation_date,
creation_user => creation_user,
creation_ip => creation_ip,
item_subtype => object_type,
title => title,
mime_type => mime_type,
text => text,
data => data,
is_live => 't'
);
-- I hate you, milkman CR.
-- Fix the broken permissions stuff content_item.new does
update acs_objects set security_inherit_p = 't'
where object_id = v_message_id;
delete from acs_permissions where object_id = v_message_id;
insert into
acs_messages (message_id, reply_to, sent_date, sender, rfc822_id)
values (v_message_id, reply_to, sent_date, sender, v_rfc822_id);
return v_message_id;
end new;
procedure delete (
message_id in acs_messages.message_id%TYPE
)
is
begin
delete from acs_messages
where message_id = acs_message.delete.message_id;
content_item.delete(message_id);
end;
function message_p (
message_id in acs_messages.message_id%TYPE
) return char
is
v_check_message_id char(1);
begin
select decode(count(message_id),0,'f','t') into v_check_message_id
from acs_messages
where message_id = message_p.message_id;
return v_check_message_id;
end message_p;
procedure send (
message_id in acs_messages.message_id%TYPE,
recipient_id in parties.party_id%TYPE,
grouping_id in integer default NULL,
wait_until in date default SYSDATE
)
is
v_wait_until date;
begin
v_wait_until := nvl(wait_until, SYSDATE);
insert into acs_messages_outgoing
(message_id, recipient_id, grouping_id, wait_until)
values
(message_id, recipient_id, grouping_id, nvl(wait_until,SYSDATE));
end;
end acs_message;
/
show errors
--
-- acs-messaging sql/upgrade-4.0.1-4.1.sql
--
-- @author John Prevost <jmp@arsdigita.com>
-- @creation-date 2001-01-16
-- @cvs-id $Id$
--
-- do all the views and packages in case something changed
@@ acs-messaging-views
@@ acs-messaging-packages
\ No newline at end of file
--
-- acs-messaging sql/upgrade-4.0.1a-4.0.1.sql
--
-- @author jmp@arsdigita.com
-- @creation-date 2000-11-15
-- @cvs-id $Id$
--
begin
acs_object_type.create_type (
supertype => 'content_revision',
object_type => 'acs_message_revision',
pretty_name => 'Message Revision',
pretty_plural => 'Message Revisions',
table_name => 'CR_REVISIONS',
id_column => 'REVISION_ID',
name_method => 'ACS_OBJECT.DEFAULT_NAME'
);
end;
/
show errors
alter table acs_messages_outgoing add (
to_address varchar2(1000)
constraint amo_to_address_nn
not null
disable
);
update acs_messages_outgoing
set to_address = (select email from parties where party_id = recipient_id);
alter table acs_messages_outgoing
drop constraint acs_messages_outgoing_pk;
alter table acs_messages_outgoing
add constraint acs_messages_outgoing_pk
primary key (message_id, to_address);
alter table acs_messages_outgoing
modify constraint amo_to_address_nn enable;
alter table acs_messages_outgoing
drop column recipient_id;
@@ acs-messaging-views
@@ acs-messaging-packages
set feedback on
This diff is collapsed.
This diff is collapsed.
--
-- packages/acs-messaging/sql/acs-messaging-create.sql
--
-- @author John Prevost <jmp@arsdigita.com>
-- @author Jon Griffin <jon@jongriffin.com>
-- @creation-date 2000-08-27
--
-- @cvs-id $Id$
-- updated for OpenACS
-- Object System Metadata ----------------------------------------------
select acs_object_type__create_type (
'acs_message',
'Message',
'Messages',
'content_item',
'ACS_MESSAGES',
'MESSAGE_ID',
null,
'f',
null,
'ACS_MESSAGE.NAME'
);
select acs_object_type__create_type (
'acs_message_revision',
'Message Revision',
'Message Revisions',
'content_revision',
'CR_REVISIONS',
'REVISION_ID',
null,
'f',
null,
'ACS_OBJECT.DEFAULT_NAME'
);
-- Raw Tables and Comments ---------------------------------------------
create table acs_messages ( -- extends cr_items
message_id integer
constraint acs_messages_message_id_fk
references cr_items (item_id) on delete cascade
constraint acs_messages_pk
primary key,
-- we will need to find a way to make reply_to go to 0 instead of null
-- to improve scalability
reply_to integer
constraint acs_messages_reply_to_fk
references acs_messages (message_id) on delete set null,
sent_date timestamptz
constraint acs_messages_sent_date_nn
not null,
sender integer
constraint acs_messages_sender_fk
references parties (party_id),
rfc822_id varchar(250)
constraint acs_messages_rfc822_id_nn
not null
constraint acs_messages_rfc822_id_un
unique,
tree_sortkey varbit
);
create index acs_messages_tree_skey_idx on acs_messages (tree_sortkey);
create index acs_messages_reply_to_idx on acs_messages (reply_to);
create index acs_messages_sender_idx on acs_messages (sender);
create index acs_messages_sent_idx on acs_messages (sent_date);
comment on table acs_messages is '
A generic message which may be attached to any object in the system.
';
comment on column acs_messages.reply_to is '
Pointer to a message this message contains a reply to, for threading.
';
comment on column acs_messages.sent_date is '
The date the message was sent (may be distinct from when it was created
or published in the system.)
';
comment on column acs_messages.sender is '
The person who sent the message (may be distinct from the person who
entered the message in the system.)
';
comment on column acs_messages.rfc822_id is '
The RFC822 message-id of this message, for sending email.
';
-- support for tree queries on acs_messages
create or replace function acs_message_get_tree_sortkey(integer) returns varbit as '
declare
p_message_id alias for $1;
begin
return tree_sortkey from acs_messages where message_id = p_message_id;
end;' language 'plpgsql' stable strict;
create or replace function acs_message_insert_tr () returns opaque as '
declare
v_parent_sk varbit default null;
v_max_value integer;
begin
if new.reply_to is null then
select max(tree_leaf_key_to_int(tree_sortkey)) into v_max_value
from acs_messages
where reply_to is null;
else
select max(tree_leaf_key_to_int(tree_sortkey)) into v_max_value
from acs_messages
where reply_to = new.reply_to;
select tree_sortkey into v_parent_sk
from acs_messages
where message_id = new.reply_to;
end if;
new.tree_sortkey := tree_next_key(v_parent_sk, v_max_value);
return new;
end;' language 'plpgsql';
create trigger acs_message_insert_tr before insert
on acs_messages
for each row
execute procedure acs_message_insert_tr ();
create function acs_message_update_tr () returns opaque as '
declare
v_parent_sk varbit default null;
v_max_value integer;
v_rec record;
clr_keys_p boolean default ''t'';
begin
if new.message_id = old.message_id and
((new.reply_to = old.reply_to) or
(new.reply_to is null and old.reply_to is null)) then
return new;
end if;
for v_rec in select message_id, reply_to
from acs_messages
where tree_sortkey between new.tree_sortkey and tree_right(new.tree_sortkey)
order by tree_sortkey
LOOP
if clr_keys_p then
update acs_messages set tree_sortkey = null
where tree_sortkey between new.tree_sortkey and tree_right(new.tree_sortkey);
clr_keys_p := ''f'';
end if;
select max(tree_leaf_key_to_int(tree_sortkey)) into v_max_value
from acs_messages
where reply_to = v_rec.reply_to;
select tree_sortkey into v_parent_sk
from acs_messages
where message_id = v_rec.reply_to;
update acs_messages
set tree_sortkey = tree_next_key(v_parent_sk, v_max_value)
where message_id = v_rec.message_id;
end LOOP;
return new;
end;' language 'plpgsql';
create trigger acs_message_update_tr after update
on acs_messages
for each row
execute procedure acs_message_update_tr ();
create table acs_messages_outgoing (
message_id integer
constraint amo_message_id_fk
references acs_messages (message_id) on delete cascade,
to_address varchar(1000)
constraint amo_to_address_nn
not null,
grouping_id integer,
wait_until timestamptz
constraint amo_wait_until_nn not null,
constraint acs_messages_outgoing_pk
primary key (message_id, to_address)
);
comment on table acs_messages_outgoing is '
Set of messages to be sent to parties. It is assumed that sending a
message either queues it in a real MTA or fails, so no information about
what''s been tried how many times is kept.
';
comment on column acs_messages_outgoing.to_address is '
The email address to send this message to. Note that this will
probably become a party_id again once upgrading a party to a user
is possible.
';
comment on column acs_messages_outgoing.grouping_id is '
This identifier is used to group sets of messages to be sent as
digests. When a message is about to be sent, any other messages
with the same grouping_id will be put together with it in a
digest. It is recommended but not required that an object id is
used. Bboard, for example, might use the forum id that the user''s
subscribed to. For instant (non-digest) updates, it would be
appropriate to use null, which is never equal to anything else.
';
comment on column acs_messages_outgoing.wait_until is '
Don''t schedule a send until after this date. If another message with
the same grouping ID is scheduled to be sent, then this message may be
sent at the same time. (So, for example, daily digests would be
achieved by setting the grouping_id to the same value, and the wait_until
value to the end of the current day. As soon as one message in the group
is to be sent, all will be sent.)
';
\i acs-messaging-views.sql
\i acs-messaging-packages.sql
--
-- packages/acs-messaging/sql/acs-messaging-drop.sql
--
-- @author akk@arsdigita.com
-- @creation-date 2000-08-31
-- @cvs-id $Id$
--
-- drop functions
drop trigger acs_message_insert_tr on acs_messages;
drop trigger acs_message_update_tr on acs_messages;
drop function acs_message_insert_tr();
drop function acs_message_update_tr();
drop function acs_message__edit (integer,varchar,varchar,varchar,
text,integer,timestamptz,integer,varchar,boolean);
drop function acs_message__new (integer,integer,timestamptz,integer,
varchar,varchar,varchar,varchar,varchar,text,
integer,integer,integer,varchar,varchar,boolean);
drop function acs_message__delete (integer);
drop function acs_message__message_p (integer);
drop function acs_message__send (integer,varchar,integer,timestamptz);
drop function acs_message__send (integer,integer,integer,timestamptz);
drop function acs_message__first_ancestor (integer);
drop function acs_message__new_file (integer,integer,varchar,varchar,
text,varchar,text,timestamptz,integer,
varchar,boolean);
drop function acs_message__edit_file (integer,varchar,text,varchar,
text,timestamptz,integer,varchar,boolean);
drop function acs_message__delete_file (integer);
drop function acs_message__new_image (integer,integer,varchar,varchar,
text,varchar,text,integer,integer,
timestamptz,integer,varchar,boolean);
drop function acs_message__edit_image (integer,varchar,text,varchar,
text,integer,integer,timestamptz,integer,
varchar,boolean);
drop function acs_message__delete_image (integer);
drop function acs_message__new_extlink (varchar,integer,varchar,varchar,text,
integer,timestamptz,integer,varchar);
drop function acs_message__edit_extlink (integer,varchar,varchar,text);
drop function acs_message__delete_extlink (integer);
drop function acs_message__name (integer);
-- drop views
drop view acs_messages_all;
drop view acs_messages_latest;
-- drop indices
drop index acs_messages_reply_to_idx;
drop index acs_messages_sender_idx;
-- drop tables
drop table acs_messages_outgoing;
drop table acs_messages;
-- drop acs_object_types
select acs_object_type__drop_type('acs_message_revision', 't');
select acs_object_type__drop_type('acs_message', 't');
--drop package acs_message;
--drop table acs_messages_outgoing;
--drop view acs_messages_all;
--drop table acs_messages;
This diff is collapsed.
--
-- packages/acs-messaging/sql/acs-messaging-create.sql
--
-- @author John Prevost <jmp@arsdigita.com>
-- @author Jon Griffin <jon@jongriffin.com>
--
-- @creation-date 2000-11-15
-- @cvs-id $Id$
--
-- Updated by Jon Griffin for OpenACS
create view acs_messages_all as
select m.message_id, m.reply_to, m.sent_date, m.sender, m.rfc822_id,
m.tree_sortkey, r.revision_id, r.title, r.mime_type, r.content
from cr_items i, cr_revisions r, acs_messages m
where i.item_id = m.message_id and r.revision_id = i.live_revision;
create view acs_messages_latest as
select m.message_id, m.reply_to, m.sent_date, m.sender, m.rfc822_id,
m.tree_sortkey, r.revision_id, r.title, r.mime_type, r.content
from cr_items i, cr_revisions r, acs_messages m
where i.item_id = m.message_id
and r.revision_id = content_item__get_latest_revision(i.item_id);
--
-- acs-messaging sql/upgrade-4.0-4.0.1a.sql
--
-- @author jmp@arsdigita.com
-- @creation-date 2000-11-03
-- @cvs-id $Id$
--
alter table acs_messages add (
sent_date date
constraint acs_messages_sent_date_nn
not null
disable,
sender integer
constraint acs_messages_sender_fk
references parties (party_id)
disable,
rfc822_id varchar2(250)
constraint acs_messages_rfc822_id_nn
not null
disable
constraint acs_messages_rfc822_id_un
unique
disable
);
create table acs_mess_up (
id integer primary key,
sent_date date,
sender integer,
rfc822_id varchar2(250)
);
insert into acs_mess_up
select m.message_id,
r.publish_date as sent_date,
o.creation_user as sender,
(sysdate || '.' || message_id || '@'
|| utl_inaddr.get_host_name||'.hate') as rfc822_id
from acs_objects o, cr_items i, cr_revisions r, acs_messages m
where m.message_id = i.item_id
and m.message_id = o.object_id
and r.revision_id = i.live_revision;
update acs_messages
set sent_date = (select sent_date from acs_mess_up where id = message_id),
sender = (select sender from acs_mess_up where id = message_id),
rfc822_id = (select rfc822_id from acs_mess_up where id = message_id);
drop table acs_mess_up;
alter table acs_messages modify constraint acs_messages_sent_date_nn enable;
alter table acs_messages modify constraint acs_messages_sender_fk enable;
alter table acs_messages modify constraint acs_messages_rfc822_id_nn enable;
alter table acs_messages modify constraint acs_messages_rfc822_id_un enable;
create or replace view acs_messages_all as
select m.message_id, m.reply_to, m.sent_date, m.sender, m.rfc822_id,
r.title, r.mime_type, r.content, o.context_id
from acs_objects o, cr_items i, cr_revisions r, acs_messages m
where o.object_id = m.message_id and i.item_id = m.message_id
and r.revision_id = i.live_revision;
create table acs_messages_outgoing (
message_id integer
constraint amo_message_id_fk
references acs_messages (message_id) on delete cascade,
recipient_id integer
constraint amo_recipient_id_fk
references parties (party_id),
grouping_id integer,
wait_until date not null,
constraint acs_messages_outgoing_pk
primary key (message_id, recipient_id)
);
comment on column acs_messages_outgoing.grouping_id is '
This identifier is used to group sets of messages to be sent as
digests. When a message is about to be sent, any other messages
with the same grouping_id will be put together with it in a
digest. It is recommended but not required that an object id is
used. Bboard, for example, might use the forum id that the user''s
subscribed to. For instant (non-digest) updates, it would be
appropriate to use null, which is never equal to anything else.
';
comment on column acs_messages_outgoing.wait_until is '
Don''t schedule a send until after this date. If another message with
the same grouping ID is scheduled to be sent, then this message may be
sent at the same time. (So, for example, daily digests would be
achieved by setting the grouping_id to the same value, and the wait_until
value to the end of the current day. As soon as one message in the group
is to be sent, all will be sent.)
';
create or replace package acs_message
as
function new (
message_id in acs_messages.message_id%TYPE default null,
reply_to in acs_messages.reply_to%TYPE default null,
sent_date in acs_messages.sent_date%TYPE default sysdate,
sender in acs_messages.sender%TYPE default null,
rfc822_id in acs_messages.rfc822_id%TYPE default null,
title in cr_revisions.title%TYPE default null,
mime_type in cr_revisions.mime_type%TYPE default 'text/plain',
text in varchar2 default null,
data in cr_revisions.content%TYPE default null,
context_id in acs_objects.context_id%TYPE,
creation_date in acs_objects.creation_date%TYPE default sysdate,
creation_user in acs_objects.creation_user%TYPE default null,
creation_ip in acs_objects.creation_ip%TYPE default null,
object_type in acs_objects.object_type%TYPE default 'acs_message'
) return acs_objects.object_id%TYPE;
procedure delete (
message_id in acs_messages.message_id%TYPE
);
function message_p (
message_id in acs_messages.message_id%TYPE
) return char;
procedure send (
message_id in acs_messages.message_id%TYPE,
recipient_id in parties.party_id%TYPE,
grouping_id in integer default NULL,
wait_until in date default SYSDATE
);
end acs_message;
/
show errors
create or replace package body acs_message
as
function new (
message_id in acs_messages.message_id%TYPE default null,
reply_to in acs_messages.reply_to%TYPE default null,
sent_date in acs_messages.sent_date%TYPE default sysdate,
sender in acs_messages.sender%TYPE default null,
rfc822_id in acs_messages.rfc822_id%TYPE default null,
title in cr_revisions.title%TYPE default null,
mime_type in cr_revisions.mime_type%TYPE default 'text/plain',
text in varchar2 default null,
data in cr_revisions.content%TYPE default null,
context_id in acs_objects.context_id%TYPE,
creation_date in acs_objects.creation_date%TYPE default sysdate,
creation_user in acs_objects.creation_user%TYPE default null,
creation_ip in acs_objects.creation_ip%TYPE default null,
object_type in acs_objects.object_type%TYPE default 'acs_message'
) return acs_objects.object_id%TYPE
is
v_message_id acs_messages.message_id%TYPE;
v_rfc822_id acs_messages.rfc822_id%TYPE;
v_name cr_items.name%TYPE;
begin
if message_id is null then
select acs_object_id_seq.nextval into v_message_id from dual;
else
v_message_id := message_id;
end if;
if rfc822_id is null then
v_rfc822_id := sysdate || '.' || v_message_id || '@' ||
utl_inaddr.get_host_name || '.hate';
else
v_rfc822_id := rfc822_id;
end if;
v_name := v_rfc822_id;
v_message_id := content_item.new (
name => v_name,
parent_id => context_id,
item_id => message_id,
creation_date => creation_date,
creation_user => creation_user,
creation_ip => creation_ip,
item_subtype => object_type,
title => title,
mime_type => mime_type,
text => text,
data => data,
is_live => 't'
);
-- I hate you, milkman CR.
-- Fix the broken permissions stuff content_item.new does
update acs_objects set security_inherit_p = 't'
where object_id = v_message_id;
delete from acs_permissions where object_id = v_message_id;
insert into
acs_messages (message_id, reply_to, sent_date, sender, rfc822_id)
values (v_message_id, reply_to, sent_date, sender, v_rfc822_id);
return v_message_id;
end new;
procedure delete (
message_id in acs_messages.message_id%TYPE
)
is
begin
delete from acs_messages
where message_id = acs_message.delete.message_id;
content_item.delete(message_id);
end;
function message_p (
message_id in acs_messages.message_id%TYPE
) return char
is
v_check_message_id char(1);
begin
select decode(count(message_id),0,'f','t') into v_check_message_id
from acs_messages
where message_id = message_p.message_id;
return v_check_message_id;
end message_p;
procedure send (
message_id in acs_messages.message_id%TYPE,
recipient_id in parties.party_id%TYPE,
grouping_id in integer default NULL,
wait_until in date default SYSDATE
)
is
v_wait_until date;
begin
v_wait_until := nvl(wait_until, SYSDATE);
insert into acs_messages_outgoing
(message_id, recipient_id, grouping_id, wait_until)
values
(message_id, recipient_id, grouping_id, nvl(wait_until,SYSDATE));
end;
end acs_message;
/
show errors
--
-- acs-messaging sql/upgrade-4.0.1-4.1.sql
--
-- @author John Prevost <jmp@arsdigita.com>
-- @creation-date 2001-01-16
-- @cvs-id $Id$
--
-- do all the views and packages in case something changed
@@ acs-messaging-views
@@ acs-messaging-packages
\ No newline at end of file
--
-- acs-messaging sql/upgrade-4.0.1a-4.0.1.sql
--
-- @author jmp@arsdigita.com
-- @creation-date 2000-11-15
-- @cvs-id $Id$
--
begin
acs_object_type.create_type (
supertype => 'content_revision',
object_type => 'acs_message_revision',
pretty_name => 'Message Revision',
pretty_plural => 'Message Revisions',
table_name => 'CR_REVISIONS',
id_column => 'REVISION_ID',
name_method => 'ACS_OBJECT.DEFAULT_NAME'
);
end;
/
show errors
alter table acs_messages_outgoing add (
to_address varchar2(1000)
constraint amo_to_address_nn
not null
disable
);
update acs_messages_outgoing
set to_address = (select email from parties where party_id = recipient_id);
alter table acs_messages_outgoing
drop constraint acs_messages_outgoing_pk;
alter table acs_messages_outgoing
add constraint acs_messages_outgoing_pk
primary key (message_id, to_address);
alter table acs_messages_outgoing
modify constraint amo_to_address_nn enable;
alter table acs_messages_outgoing
drop column recipient_id;
@@ acs-messaging-views
@@ acs-messaging-packages
set feedback on
This diff is collapsed.
-- DRB: The drop is needed otherwise we'll get ambiguous function
-- errors on calls from query files using bindvar emulation, where
-- all the parameters are quoted and therefore of unknown type.
create or replace function acs_message__new (integer,integer,timestamptz,integer,
varchar,varchar,varchar,varchar,text,integer,integer,integer,integer,
varchar,varchar,boolean)
returns integer as '
declare
p_message_id alias for $1; --default null,
p_reply_to alias for $2; --default null,
p_sent_date alias for $3; --default sysdate,
p_sender alias for $4; --default null,
p_rfc822_id alias for $5; --default null,
p_title alias for $6; --default null,
p_description alias for $7; --default null,
p_mime_type alias for $8; --default ''text/plain'',
p_text alias for $9; --default null,
p_data alias for $10; --default null,
p_parent_id alias for $11; --default 0,
p_context_id alias for $12;
p_creation_date timestamptz := current_timestamp; -- alias for $13 --default sysdate,
p_creation_user alias for $13; --default null,
p_creation_ip alias for $14; --default null,
p_object_type alias for $15; --default ''acs_message'',
p_is_live alias for $16; --default ''t''
v_message_id acs_messages.message_id%TYPE;
v_rfc822_id acs_messages.rfc822_id%TYPE;
v_revision_id cr_revisions.revision_id%TYPE;
v_system_url varchar;
v_domain_name varchar;
v_idx integer;
begin
-- generate a message id now so we can get an rfc822 message-id
if p_message_id is null then
select acs_object_id_seq.nextval into v_message_id;
else
v_message_id := p_message_id;
end if;
-- need to make this mandatory also - jg
-- this needs to be fixed up, but Oracle doesn''t give us a way
-- to get the FQDN
-- vk: get SystemURL parameter and use it to extract domain name
select apm__get_value(package_id, ''SystemURL'') into v_system_url
from apm_packages where package_key=''acs-kernel'';
v_idx := position(''http://'' in v_system_url);
v_domain_name := trim (substr(v_system_url, v_idx + 7));
if p_rfc822_id is null then
v_rfc822_id := current_date || ''.'' || v_message_id || ''@'' ||
v_domain_name || ''.hate'';
else
v_rfc822_id := p_rfc822_id;
end if;
v_message_id := content_item__new (
v_rfc822_id, -- name
p_parent_id, -- parent_id
p_message_id, -- item_id
null, -- locale
p_creation_date, -- creation_date
p_creation_user, -- creation_user
p_context_id, -- context_id
p_creation_ip, -- creation_ip
p_object_type, -- item_subtype
''acs_message_revision'', -- content_type
null, -- title
null, -- description
''text/plain'', -- mime_type
null, -- nls_language
null, -- text
''text'' -- storage_type
);
insert into acs_messages
(message_id, reply_to, sent_date, sender, rfc822_id)
values
(v_message_id, p_reply_to, p_sent_date, p_sender, v_rfc822_id);
-- create an initial revision for the new message
v_revision_id := acs_message__edit (
v_message_id, -- message_id
p_title, -- title
p_description, -- description
p_mime_type, -- mime_type
p_text, -- text
p_data, -- data
p_creation_date, -- creation_date
p_creation_user, -- creation_user
p_creation_ip, -- creation_ip
p_is_live -- is_live
);
return v_message_id;
end;' language 'plpgsql';
-- call image__delete instead.
create or replace function acs_message__delete_image (integer)
returns integer as '
declare
p_image_id alias for $1;
begin
perform image__delete(p_image_id);
return 1;
end;' language 'plpgsql';
ad_library {
Set up a scheduled process to send out email messages.
@cvs-id $Id$
@author John Prevost <jmp@arsdigita.com>
@creation-date 2000-10-28
}
# Schedule every 15 minutes. Its own thread. since ns_sendmail does
# network activity.
ad_schedule_proc -thread t 900 acs_messaging_process_queue
<?xml version="1.0"?>
<queryset>
<rdbms><type>oracle</type><version>8.1.6</version></rdbms>
<fullquery name="acs_messaging_send_query.insert_messaging_by_query">
<querytext>
insert into acs_messages_outgoing
(message_id, to_address, grouping_id, wait_until)
select :m__message_id, p.email, q.grouping_id,
nvl(q.wait_until, SYSDATE) as wait_until
from ($query) q, parties p
where not exists (select 1 from acs_messages_outgoing o
where o.message_id = :m__message_id
and p.email = o.to_address)
and p.party_id = q.recipient_id
</querytext>
</fullquery>
<fullquery name="acs_message_p.acs_message_p">
<querytext>
begin
:1 := acs_message.message_p(:message_id);
end;
</querytext>
</fullquery>
<fullquery name="acs_messaging_first_ancestor.acs_message_first_ancestor">
<querytext>
select acs_message.first_ancestor(:message_id) as ancestor_id from dual
</querytext>
</fullquery>
<fullquery name="acs_messaging_send_query.insert_messaging_by_query">
<querytext>
insert into acs_messages_outgoing
(message_id, to_address, grouping_id, wait_until)
select :m__message_id, p.email, q.grouping_id,
nvl(q.wait_until, SYSDATE) as wait_until
from ($query) q, parties p
where not exists (select 1 from acs_messages_outgoing o
where o.message_id = :m__message_id
and p.email = o.to_address)
and p.party_id = q.recipient_id
</querytext>
</fullquery>
<fullquery name="acs_messaging_process_queue.acs_message_send">
<querytext>
select o.message_id as sending_message_id,
o.to_address as recip_email,
p.email as sender_email,
to_char(m.sent_date, 'Dy, DD Mon YYYY HH24:MI:SS') as sent_date,
m.rfc822_id,
m.title,
m.mime_type,
m.content,
m2.rfc822_id as in_reply_to
from acs_messages_outgoing o,
acs_messages_all m,
acs_messages_all m2,
parties p
where o.message_id = m.message_id
and m2.message_id(+) = m.reply_to
and p.party_id = m.sender
and wait_until <= sysdate
</querytext>
</fullquery>
</queryset>
<?xml version="1.0"?>
<queryset>
<rdbms><type>postgresql</type><version>7.1</version></rdbms>
<fullquery name="acs_messaging_send_query.insert_messaging_by_query">
<querytext>
insert into acs_messages_outgoing
(message_id, to_address, grouping_id, wait_until)
select :m__message_id, p.email, q.grouping_id,
coalesce(q.wait_until, current_timestamp) as wait_until
from ($query) q, parties p
where not exists (select 1 from acs_messages_outgoing o
where o.message_id = :m__message_id
and p.email = o.to_address)
and p.party_id = q.recipient_id
</querytext>
</fullquery>
<fullquery name="acs_message_p.acs_message_p">
<querytext>
select acs_message__message_p(:message_id);
</querytext>
</fullquery>
<fullquery name="acs_messaging_first_ancestor.acs_message_first_ancestor">
<querytext>
select acs_message__first_ancestor(:message_id) as ancestor_id
</querytext>
</fullquery>
<fullquery name="acs_messaging_process_queue.acs_message_send">
<querytext>
select o.message_id as sending_message_id,
o.to_address as recip_email,
p.email as sender_email,
to_char(m.sent_date, 'Dy, DD Mon YYYY HH24:MI:SS') as sent_date,
m.rfc822_id,
m.title,
m.mime_type,
m.content,
m2.rfc822_id as in_reply_to
from acs_messages_outgoing o,
acs_messages_all m left outer join acs_messages_all m2 on (m2.message_id = m.reply_to),
parties p
where o.message_id = m.message_id
and p.party_id = m.sender
and wait_until <= current_timestamp
</querytext>
</fullquery>
</queryset>
ad_library {
Utility procs for working with messages
@author John Prevost <jmp@arsdigita.com>
@creation-date 2000-09-01
@cvs-id $Id$
}
ad_proc -public acs_message_p {
{message_id}
} {
Check if an integer is a valid OpenACS message id.
} {
return [string equal [db_exec_plsql acs_message_p {
begin
:1 := acs_message.message_p(:message_id);
end;
}] "t"]
}
ad_page_contract_filter acs_message_id { name value } {
Checks whether the value (assumed to be an integer) is the id
of an already-existing OpenACS message.
} {
# empty is okay (handled by notnull)
if [empty_string_p $value] {
return 1
}
if ![acs_message_p $value] {
ad_complain "$name ($value) does not refer to a valid OpenACS message"
return 0
}
return 1
}
ad_proc -public acs_messaging_format_as_html {
{mime_type}
{content}
} {
Returns a string of HTML which appropriately renders the content
given its mime content-type. This function supports three content
types, "text/plain", "text/plain; format=flowed", and "text/html"
@param mime_type MIME content-type of content
@param content Text to view
} {
if {[string eq $mime_type "text/plain"]} {
set result "<pre>[ad_quotehtml $content]</pre>"
} elseif {[string eq $mime_type "text/plain; format=flowed"]} {
set result [ad_text_to_html -- $content]
} elseif {[string eq $mime_type "text/html"]} {
set result $content
} else {
set result "<i>content type undecipherable</i>"
}
return $result
}
ad_proc -public acs_messaging_first_ancestor {
{message_id}
} {
Takes the message_id of an acs_message and returns
the message_id of the first ancestor message (i.e. the message
that originated the thread).
} {
db_1row acs_message_first_ancestor {
select acs_message.first_ancestor(:message_id) as ancestor_id from dual
}
return $ancestor_id
}
ad_proc -public acs_messaging_send {
{-message_id:required}
{-recipient_id:required}
{-grouping_id ""}
{-wait_until ""}
} {
Schedule one message to be sent to one party.
} {
db_dml {
begin
acs_message.send (
message_id => :message_id,
recipient_id => :recipient_id,
grouping_id => :grouping_id,
wait_until => :wait_until
);
end;
}
}
ad_proc -public acs_messaging_send_query {
{-message_id:required}
{-query:required}
{-bind ""}
} {
Given an SQL query returning columns recipient_id, grouping_id,
and wait_until, arrange for all to be sent for this message.
Example:
acs_message_send_query -message_id $new_message -query {
select subscriber_id as recipient_id, forum_id as grouping_id,
bboard_util.next_period(period) as wait_until
from bboard_forum_subscribers
where forum_id = :forum_id
} -bind [list forum_id $forum_id]
Assuming bboard_util.next_period(period) returns the next date at
which a digest should be sent, the above will enter info to send
all subscriptions for a single message.
The bind argument, if given, must be a list, NOT an ns_set.
} {
# Makes sure not to insert values that are already there--silent "failure"
# because it's really a vacuous success.
db_dml insert_messaging_by_query "
insert into acs_messages_outgoing
(message_id, to_address, grouping_id, wait_until)
select :m__message_id, p.email, q.grouping_id,
nvl(q.wait_until, SYSDATE) as wait_until
from ($query) q, parties p
where not exists (select 1 from acs_messages_outgoing o
where o.message_id = :m__message_id
and p.email = o.to_address)
and p.party_id = q.recipient_id
" -bind [concat $bind [list m__message_id $message_id]]
}
ad_proc -private acs_messaging_timezone_offset {
} {
Returns a best guess of the timezone offset for the machine.
} {
return [format "%+05d" [expr ([lindex [ns_localtime] 2] - [lindex [ns_gmtime] 2]) * 100]]
}
ad_proc -private acs_messaging_process_queue {
} {
Process the message queue, sending any reasonable messages.
} {
db_foreach acs_message_send {
select o.message_id as sending_message_id,
o.to_address as recip_email,
p.email as sender_email,
to_char(m.sent_date, 'Dy, DD Mon YYYY HH24:MI:SS') as sent_date,
m.rfc822_id,
m.title,
m.mime_type,
m.content,
m2.rfc822_id as in_reply_to
from acs_messages_outgoing o,
acs_messages_all m,
acs_messages_all m2,
parties p
where o.message_id = m.message_id
and m2.message_id(+) = m.reply_to
and p.party_id = m.sender
and wait_until <= sysdate
} {
# Need to process content to do CRLF conversions?
set headers [ns_set create]
ns_set put $headers Sender [ad_parameter "OutgoingSender" "acs-kernel"]
if ![string equal $in_reply_to ""] {
ns_set put $headers In-Reply-To "<$in_reply_to>"
}
ns_set put $headers Message-ID "<$rfc822_id>"
ns_set put $headers Date "$sent_date [acs_messaging_timezone_offset]"
ns_set put $headers MIME-Version "1.0"
ns_set put $headers Content-Type $mime_type
ns_log "Notice" "About to send"
if ![catch {
ns_sendmail $recip_email $sender_email $title $content $headers
} errMsg] {
ns_log "Notice" "Sending"
# everything went well, dequeue
db_dml acs_message_remove_from_queue {
delete from acs_messages_outgoing
where message_id = :sending_message_id
and to_address = :recip_email
}
} else {
ns_log "Notice" "Not sending: $errMsg"
}
}
}
<?xml version="1.0"?>
<queryset>
<fullquery name="acs_messaging_process_queue.acs_message_remove_from_queue">
<querytext>
delete from acs_messages_outgoing
where message_id = :sending_message_id
and to_address = :recip_email
</querytext>
</fullquery>
</queryset>
ad_library {
Automated tests.
@author Joel Aufrecht
@creation-date 2 Nov 2003
@cvs-id $Id$
}
aa_register_case acs_messaging_format_as_html {
Test acs_messaging_format_as_html proc.
} {
aa_run_with_teardown \
-rollback \
-test_code {
# initialize random values
set name [ad_generate_random_string]
set formatted_name [acs_messaging_format_as_html text/html $name]
aa_true "Name is formatted" ![string match "<pre>$name<pre>" $formatted_name]
}
}
aa_register_case acs_messaging_message_p {
Test message_p proc.
} {
aa_run_with_teardown \
-rollback \
-test_code {
set message_p [acs_message_p "0"]
aa_true "Integer is not a message_id" !$message_p
}
}
<html>
<head>
<title>ACS Messaging Design</title>
</head>
<body bgcolor="#ffffff">
<h2>ACS Messaging Design</h2>
<hr />
ACS Messaging was born out of the design of the new bboard. One
thing we discovered when researching requirements for bboard and
discussion software in general was that there are a variety of
ways one may wish to structure and organize threads of messages
e.g. in discrete forums with tagged categories, attached to other
user objects annotated with user ratings, etc.,. Our design
addressed this by separating the store of messages from the
organizational data model and any user interfaces.<p />
ACS Messaging is this separate layer. Built atop the content
repository, it provides the storage and retrieval of messages. We
take messages to be objects that consist of a sender (an ACS
party), a text body, an optional reference to a parent message,
optional file attachments, and some miscellaneous auditing data.<p />
With these constraining constraining set of semantics, we can
build a library of component functionality to operate on messages.
For example: code that displays a message, forwards a message,
compiles a set of messages into a digest, displays a specific
attachment, etc., This functionality can then be reused across
messaging applications such as bboard, webmail, and general
comments. We can maintain user preferences on HTML vs. text email,
inline attachments vs. URLs across the system, and have simple
procedures that do the right thing when sending email. Another
example: if we built the IMAP server functionality 3.4 webmail
provides against acs-messaging, then bboard forums, pages of
comments, and webmail folders could be viewed uniformly through
your email client. The IMAP mapping isn't quite trivial, but you
can see the idea.<p />
To reiterate, if applications are storing the same sort of data (a
text-ish messages with optional attachments and replies), they
should store them the same way. Then code from particular
applications can possibly be refactored into generic
functionality.<p />
spam/general alerts/etc isn't meant to be replaced by ACS
Messaging, at least not with what is there currently. Currently
it is just a store; but we intend it to be the canonical store for
messages that need to be stored in the database. If messages are
automatically generated from other user objects, they might need
to be queue'd up or archived in the RDBMS. If so this should be
done in the acs-messaging tables. We can implement the generic
incoming email system by stashing messages in acs-messaging, then
dispatching the message id to package specific code for
processing.<p />
Currently (11/2000), ACS Messaging is very slim; it just supports
bboard. We intend to add attachments (most likely implemented as
content repository items that are children of the message),
extensible headers (just like the webmail datamodel), and
versioning as provided by the content repository.<p />
<h2>API</h2>
ACS Messaging provides the <code>acs_messages_all</code> view as the
primary mechanism for message queries.
<blockquote><code><pre>
create or replace view acs_messages_all as
select m.message_id, m.reply_to, o.context_id, r.title, r.publish_date,
r.mime_type, r.content, o.creation_user
...
</pre></code></blockquote>
ACS Messaging provides the PL/SQL function acs_message.post to
add new messages.
<hr />
<address>akk@arsdigita.com</address>
</body>
</html>
\ No newline at end of file
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<title>ACS Messaging Docs</title>
</head>
<body>
<h1>ACS Messaging Docs</h1>
<ul>
<li><a href="requirements">requirements</a>
<li><a href="../bboard/design">bboard design</a>
</ul>
<hr>
<address><a href="mailto:akk@arsdigita.com">Anukul Kapoor</a></address>
<!-- Created: Sat Sep 30 16:42:40 EDT 2000 -->
<!-- hhmts start -->
Last modified: Sat Sep 30 17:45:40 EDT 2000
<!-- hhmts end -->
</body>
</html>
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment