Creating a Web-Based BBS, Part 3
Whenever I work on a web site that involves databases, I almost always include one or two web-based administrative tools. Such tools are useful for a number of reasons, the most obvious of which is their utility for people who want to manipulate the database without learning SQL. (They also allow you to shield the database from people who think they understand SQL, only to discover there is not any way to undo a DROP TABLE command.)
Which tools are necessary and which functions they must perform will depend on the web application you have written, as well as the needs of your individual users. We will look at a simple application called zap-thread.pl, which allows administrators to delete one or more discussion threads.
In order to ensure that only authorized users can zap threads, we will include a password field, adding the variable $zap_password to ATFConstants.pm, the module containing all of our global variables. (See Listing 4 in the archive file.)
While there are a number of ways in which we could implement zap-thread.pl, I found it easiest to write a single program that has two different personalities. When invoked with the GET method, zap-thread.pl produces an HTML form that can be used to delete threads, including a text field into which the user must enter a password.
When invoked with POST, zap-thread.pl assumes it was invoked by the form produced by its GET personality. It expects to receive two different sorts of HTML form elements: a password in the password form element, which must be compared with the $zap_password variable, and one or more check boxes named “thread-x”, where x is the ID number of the thread in the database.
Before doing anything else, our program compares the password it received with the $zap_password variable. If they match, we continue without comment. If they fail to match, we produce an error message telling the user that the password did not match.
We can iterate through the thread-x elements in a number of ways, but I found the easiest way was to iterate through each element, ignoring any elements that failed to match the “thread-x” pattern. By capturing the number within parentheses:
next unless ($element =~ m/^thread-(\d+)$/);
we can then grab it using the $1 variable, which retrieves whatever was in the first set of parentheses in the last match:
my $thread_id = $1;Once we have retrieved the thread ID number, we need to delete matching rows from ATFThreads, which contains the master list of threads, and from ATFMessages, which contains the messages themselves. If we were to delete rows from only ATFThreads, we would run a substantial risk of MySQL reusing the thread ID number with a new thread—which would effectively put all of our supposedly deleted messages in the new thread.
We delete them by sending two separate SQL queries, printing a brief status message to the user's browser:
my $sql = "DELETE FROM ATFThreads WHERE id = $thread_id "; warn "SQL: \"$sql\"\n"; my $sth = $dbh->prepare ($sql); my $result = $sth->execute; die("Error deleting from ATFThreads: " . $sth->errstr) unless $result; print "<P>Deleted the thread.</P>\n"; # Delete messages for this thread from ATFMessages $sql = "DELETE FROM ATFMessages WHERE thread = $thread_id "; warn "SQL: \"$sql\"\n"; $sth = $dbh->prepare ($sql); $result = $sth->execute; die("Error deleting from ATFMessages: " . $sth->errstr) unless $result; print "<P>Deleted messages in the thread.</P>\n"; }
By putting the DELETE commands inside of a foreach loop, we make it possible to delete more than one thread. If the user indicates that three threads should be deleted, we will enter the loop three times, deleting each thread in sequence.
There is at least one problem with this implementation of zap-thread.pl, and that is the lack of an undo function. What happens if you delete the wrong thread? Perhaps the easiest way to implement such an undelete function would be to change the underlying tables, adding a new “active” column to ATFThreads and ATFMessages. This column would contain a single true/false value, perhaps implemented with a TINYINT or an ENUM type.
With such table definitions in place, deleting a thread would involve an UPDATE query (rather than the current DELETE query) to modify the value in that column. Such a change would also require some adjustments to list-threads.pl and view-thread.pl, so that they only SELECT those rows WHERE active="true", or a similar condition.
Another potential issue with zap-thread.pl is that it operates on a per-thread basis. There are undoubtedly times when we will want to delete an individual message, rather than a whole thread. Creating such a program would be a bit more difficult than zap-thread.pl, but the true challenge is in the user interface. Using zap-thread.pl will be difficult enough with 100 threads; trying to find an interface appropriate for zapping individual messages would be even more difficult. The best approach might involve breaking the program into two smaller programs, one for selecting a thread and another for selecting a message.
As with most software projects, this BBS has almost unlimited potential for improvement and expansion. A number of ways remain in which this software could be improved—allowing for sub-threads, providing for moderated discussions, sending e-mail to some or all participants in a thread when a new posting is added, allowing users to edit their own postings and even an integrated spell-checker.
Finally, notice how our web BBS application used separate CGI programs, rather than one single large program. Using a suite of related programs is not the only way to design such applications, but I find it to be the easiest way to create such functionality. Not only does it allow breaking the problem into separate slices, but it also allows for incremental implementation—which comes in handy when a client is breathing down your neck, wanting to see results right away.
|Speed Up Your Web Site with Varnish||Jun 19, 2013|
|Non-Linux FOSS: libnotify, OS X Style||Jun 18, 2013|
|Containers—Not Virtual Machines—Are the Future Cloud||Jun 17, 2013|
|Lock-Free Multi-Producer Multi-Consumer Queue on Ring Buffer||Jun 12, 2013|
|Weechat, Irssi's Little Brother||Jun 11, 2013|
|One Tail Just Isn't Enough||Jun 07, 2013|
- Speed Up Your Web Site with Varnish
- Containers—Not Virtual Machines—Are the Future Cloud
- Linux Systems Administrator
- Lock-Free Multi-Producer Multi-Consumer Queue on Ring Buffer
- RSS Feeds
- Senior Perl Developer
- Technical Support Rep
- Non-Linux FOSS: libnotify, OS X Style
- UX Designer
- So when they found it hard to
5 min 54 sec ago
28 min 5 sec ago
- Reply to comment | Linux Journal
50 min 24 sec ago
- Android has been dominating
54 min 56 sec ago
- It is quiet helping
3 hours 40 min ago
3 hours 57 min ago
- Reachli - Amplifying your
5 hours 14 min ago
6 hours 2 min ago
- good point!
6 hours 5 min ago
- Varnish works!
6 hours 14 min ago
Free Webinar: Hadoop
How to Build an Optimal Hadoop Cluster to Store and Maintain Unlimited Amounts of Data Using Microservers
Realizing the promise of Apache® Hadoop® requires the effective deployment of compute, memory, storage and networking to achieve optimal results. With its flexibility and multitude of options, it is easy to over or under provision the server infrastructure, resulting in poor performance and high TCO. Join us for an in depth, technical discussion with industry experts from leading Hadoop and server companies who will provide insights into the key considerations for designing and deploying an optimal Hadoop cluster.
Some of key questions to be discussed are:
- What is the “typical” Hadoop cluster and what should be installed on the different machine types?
- Why should you consider the typical workload patterns when making your hardware decisions?
- Are all microservers created equal for Hadoop deployments?
- How do I plan for expansion if I require more compute, memory, storage or networking?