Initial revision

This commit is contained in:
briandilley 2004-10-14 22:20:03 +00:00
commit bf921de540
18 changed files with 916 additions and 0 deletions

5
Changelog Normal file
View File

@ -0,0 +1,5 @@
10-10-2004
* Added kwote_backup table.
* Implemented functionality to remove nagative kwotes.

View File

@ -0,0 +1,2 @@
Thank you for adding your kwote, here's a link to it: <a href="?action=show&amp;id=${KWOTE_ID}">#${KWOTE_ID}</a>

View File

@ -0,0 +1,7 @@
<div class="search-container">
<form action="${SCRIPT_NAME}" method="POST">
<input type="hidden" name="action" value="doadd" />
<textarea rows="15" cols="60" name="content"></textarea><br />
<input type="submit" value="Submit" />
</form>
</div>

40
html/content-default.html Normal file
View File

@ -0,0 +1,40 @@
<div>
<h3>Welcome</h3>
We need to add some content here, soon! Anyway, this is kwotes. It's
a chat quote database system, with a twist. This chat quote database
is publicly moderated... so ALL of your submissions are accepted, and
it's up to the general public to determine wether or not it's funny...
no stupid moderators who wouldn't know funny if it grabed them by the
asshole and ripped them apart. Anyway, this system has some advantages
over existing systems, here are a few:
<ul>
<li>
You can vote for a quote without it going to a new page and
you losing your place in your current view.
</li>
<li>
The system is publicly moderated so that all submissions are
accepted, submissions that are less than zero for more than
a certain period of time are removed from the system (maybe...)
</li>
<li>
Users may only submit 4 quotes per hour.
</li>
<li>
The system is opensource, you can download the source code
using the link at the bottom of the page. Work is being
done to setup a CVS repository and such.
</li>
<li>
Slim sleek design... all browsers like us :)
</li>
</ul>
We're thinking about creating an egdrop script for auto submiting
quotes to the system, we'll see how that goes :)
</div>

3
html/content-error.html Normal file
View File

@ -0,0 +1,3 @@
<h1>${ERROR_MESSAGE}</h1>

View File

@ -0,0 +1,4 @@
<div class="kwote-navigation">
<a href="?action=list&amp;o=${ORDER}&amp;s=${NEXT_INDEX}&amp;m=${MAX_RETURN}&amp;mr=${MAX_RECORDS}">Next &gt;</a>
</div>

View File

@ -0,0 +1,4 @@
<div class="kwote-navigation">
<a href="?action=list&amp;o=${ORDER}&amp;s=${LAST_INDEX}&amp;m=${MAX_RETURN}&amp;mr=${MAX_RECORDS}">&lt; Previous</a>
</div>

View File

@ -0,0 +1,6 @@
<div class="kwote-navigation">
<a href="?action=list&amp;o=${ORDER}&amp;s=${LAST_INDEX}&amp;m=${MAX_RETURN}&amp;mr=${MAX_RECORDS}">&lt; Previous</a>
&nbsp; | &nbsp;
<a href="?action=list&amp;o=${ORDER}&amp;s=${NEXT_INDEX}&amp;m=${MAX_RETURN}&amp;mr=${MAX_RECORDS}">Next &gt;</a>
</div>

28
html/content-search.html Normal file
View File

@ -0,0 +1,28 @@
<form action="${SCRIPT_NAME}" method="get">
<div class="search-container">
<input type="hidden" name="action" value="list" />
<input type="hidden" name="s" value="0" />
<span>Search:&nbsp;</span><input type="text" name="ss" />&nbsp;<input type="submit" value="Search" />
&nbsp;&nbsp;
Kwotes per page:
<select name="m">
<option value="5">5</option>
<option value="20">20</option>
<option value="30">30</option>
<option value="40">40</option>
<option value="50">50</option>
<option value="100">100</option>
<option value="200">200</option>
</select>
&nbsp;&nbsp;
Sort by:
<select name="o">
<option value="date">Date</option>
<option value="rating">Rating</option>
</select>
</div>
</form>

View File

@ -0,0 +1,13 @@
<div class="quote">
<div class="quote-header">
<span><a href="?action=show&amp;id=${KWOTE_ID}">#${KWOTE_ID}</a></span>
<span class="vote-controls">
<a href="javascript:vote(${KWOTE_ID},'hate');" id="hate${KWOTE_ID}">-</a>
&nbsp;<span id="rating${KWOTE_ID}">${KWOTE_RATING}</span>&nbsp;
<a href="javascript:vote(${KWOTE_ID},'love');" id="love${KWOTE_ID}">+</a>
</span>
</div>
<div class="quote-content">
${KWOTE_TEXT}
</div>
</div>

12
html/footer.html Normal file
View File

@ -0,0 +1,12 @@
</div>
<div class="footer">
<a href="http://validator.w3.org/check?uri=referer">Valid XHTML 1.0 Strict</a>
&nbsp;-&nbsp;
<a href="kwotes.tar.gz">download the source</a>
&nbsp;-&nbsp;
${KWOTE_COUNT} live kwotes, ${KWOTE_BACKUP_COUNT} deleted kwotes
</div>
</div>
</body>
</html>

59
html/header.html Normal file
View File

@ -0,0 +1,59 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
<head>
<meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>${TITLE}</title>
<link rel="stylesheet" type="text/css" href="html/quotes.css" />
<!--[if IE]>
<link rel="stylesheet" type="text/css" href="html/quotes-ie.css" />
<![endif]-->
<script type="text/javascript">
function vote(kid,type) {
var img = new Image();
img.src = "?action="+escape(type)+unescape("%26")+"kid="+escape(kid);
hideElementById("hate"+kid);
hideElementById("love"+kid);
var ratingElem = document.getElementById("rating"+kid);
if (ratingElem) {
var html = ratingElem.innerHTML;
var rating = parseInt(html)+( (type=="love")?1:-1 );
ratingElem.innerHTML = rating;
}
}
function hideElementById(id) {
var elem = document.getElementById(id);
if (elem) {
elem.style.display = "none";
}
}
</script>
</head>
<body>
<div class="container">
<span style="font-weight: bold;">Kwotes.org - The publicly moderated chat quote database</span>
<ul class="tab-list">
<li class="tab"><a href="?">Home</a></li>
<li class="tab"><a href="?action=add">Add</a></li>
<li class="tab"><a href="?action=list&amp;o=date">Latest</a></li>
<li class="tab"><a href="?action=list&amp;o=rating&amp;s=0&amp;m=20&amp;mr=50">Top 100</a></li>
<li class="tab"><a href="?action=list&amp;o=rating&amp;s=0&amp;m=20&amp;mr=50&amp;so=reverse">Bottom 100</a></li>
<li class="tab"><a href="?action=search">Search</a></li>
</ul>
<form action="${SCRIPT_NAME}" method="get">
<div class="direct-form">
<span>Kwote #</span>
<input type="hidden" name="action" value="show" />
<input type="text" name="id" />
</div>
</form>
<div class="body-container">

11
html/quotes-ie.css Normal file
View File

@ -0,0 +1,11 @@
/* Stupid IE stuff... at least they get to see something hilite */
.tab a:active
{
background-color: #8b8bca;
}
.tab a:hover
{
background-color: #dfdff0;
}
/* End stupid IE stuff */

122
html/quotes.css Normal file
View File

@ -0,0 +1,122 @@
body
{
font-family: Trebuchet MS, Tahoma, Arial, Sans-Serif;
margin: 0px;
}
a:link
{
text-decoration: none;
}
a:visited
{
text-decoration: none;
}
a:hover, a:active
{
text-decoration: underlined;
}
/* Begin Containers */
.container
{
width: 700px;
position: relative;
margin-left: auto;
margin-right: auto;
text-align: left;
padding: 2px 2px 5px 2px;
}
.body-container
{
position: relative;
margin-top: 5px;
margin-bottom: 5px;
}
/* End Containers */
.direct-form
{
position: absolute;
top: 1px;
right: 4px;
font-size: 0.8em;
}
.direct-form input
{
background-color: #dfdff0;
border: 1px solid black;
}
.quote
{
position: relative;
margin-bottom: 15px;
}
.quote-header
{
position: relative;
border-bottom: 1px solid black;
font-size: 0.8em;
}
.vote-controls
{
position: absolute;
top: 0px;
right: 0px;
}
.quote-content
{
font-size: 0.9em;
}
.footer
{
font-size: 0.8em;
margin-top: 10px;
}
/* Horizontal Menu */
.tab-list
{
margin: 0px;
padding: 0px;
position: relative;
list-style-type: none;
background-color: #bfbfe2;
height: 18;
}
/* This is used by older browsers & IE */
.tab-list .tab
{
display: inline;
margin: 0px;
padding-left: 3px;
padding-right: 3px;
font-size: 0.7em;
height: 18px;
}
/* Good browsers use this too */
.tab-list .tab
{
display: table-cell;
}
.tab:active
{
background-color: #8b8bca;
}
.tab:hover
{
background-color: #dfdff0;
}

326
kwotes-lib.pl Executable file
View File

@ -0,0 +1,326 @@
#!/usr/bin/perl
# we use DBI, for it's sexy body
use DBI;
# database connection
my $GLOBAL_DBH = undef;
##
# Returns the kwote count
sub get_kwote_count {
# connect
my $dbh = get_db_connection();
# execute
my $sth = $dbh->prepare("SELECT COUNT(*) as kwote_count FROM kwote");
$sth->execute();
# return
my $row = $sth->fetchrow_hashref();
return $row->{"kwote_count"};
}
##
# Returns the kwote_backup count
sub get_kwote_backup_count {
# connect
my $dbh = get_db_connection();
# execute
my $sth = $dbh->prepare("SELECT COUNT(*) as kwote_count FROM kwote_backup");
$sth->execute();
# return
my $row = $sth->fetchrow_hashref();
return $row->{"kwote_count"};
}
##
# does some minor database cleanup
sub cleanup {
# get a db connection
my $dbh = get_db_connection();
# backup kwotes to be deleted
my $sth = $dbh->prepare(
"INSERT INTO kwote_backup SELECT * FROM kwote WHERE ".
"(now()-submit_dt)>? AND rating<0"
);
$sth->bind_param(1, NEGATIVE_KWOTE_TTL);
$sth->execute() or die "Couldn't backup kwotes";
# delete kwotes
$sth = $dbh->prepare(
"DELETE FROM kwote WHERE (now()-submit_dt)>? AND rating<0"
);
$sth->bind_param(1, NEGATIVE_KWOTE_TTL);
$sth->execute() or die "Couldn't delete kwotes";
# delete the vote log (this doesn't affect kwote rating)
$sth = $dbh->prepare(
"DELETE FROM vote WHERE (now()-vote_dt)>?"
);
$sth->bind_param(1, VOTE_TTL);
$sth->execute() or die "Couldn't delete votes";
# let em know we're good
print "Kwote Database cleanup complete\n";
# w00t
return 0;
}
##
# votes on a kwote
sub vote {
my ($addr, $kid, $amt) = @_;
# connect to db
my $dbh = get_db_connection();
# prepare statement
my $sth = $dbh->prepare(
"SELECT COUNT(*) as vote_count FROM vote WHERE ".
"ip_address=? AND kwote_id=?"
);
$sth->bind_param(1, $addr);
$sth->bind_param(2, $kid);
# execute
$sth->execute();
# get row
my $row = $sth->fetchrow_hashref();
# check if they suck
return undef if ($row->{"vote_count"}>=MAX_VOTES_PER_IP);
# prepare
$sth = $dbh->prepare(
"UPDATE kwote SET rating=rating+(?) WHERE id=?"
);
$sth->bind_param(1, $amt);
$sth->bind_param(2, $kid);
$sth->execute() or return undef;
# record the vote
$sth = $dbh->prepare(
"INSERT INTO vote (ip_address, kwote_id, vote_dt) ".
"VALUES (?, ?, now())"
);
$sth->bind_param(1, $addr);
$sth->bind_param(2, $kid);
$sth->execute() or return undef;
# we're good
return 1;
}
##
# adds a kwote to the database
sub add_kwote {
my ($dbh, $kwote_text, $ip_address) = @_;
my ($addr, $kid, $amt) = @_;
# make sure the kwote is ok
return undef if (!defined($kwote_text) || $kwote_text eq "");
# prepare statement
my $sth = $dbh->prepare(
"SELECT COUNT(*) as kwote_count FROM kwote WHERE ip_address=? AND (now()-submit_dt)<?"
);
$sth->bind_param(1, $ip_address);
$sth->bind_param(2, SECS_BETWEEN_KWOTES);
# execute
$sth->execute() or return undef;
# get row
my $row = $sth->fetchrow_hashref() or return undef;
# check if they suck
return undef if ($row->{"kwote_count"}>=MAX_KWOTES_PER_IP);
# prepare statement
my $sth = $dbh->prepare(
"INSERT INTO kwote (submit_dt, content, rating, ip_address) ".
"VALUES (now(), ?, ?, ?)"
) or return undef;
# bind params
$sth->bind_param(1, $kwote_text); # this is the kwote text
$sth->bind_param(2, 0); # no rating as of yet
$sth->bind_param(3, $ip_address); # the ip address
# execute
$sth->execute() or return undef;
# return the id
return $dbh->{insertid};
}
##
# adds a kwote to the database
sub get_kwote {
my ($dbh, $kid) = @_;
# prepare statement
my $sth = $dbh->prepare(
"SELECT * FROM kwote WHERE id=?"
) or return undef;
# bind params
$sth->bind_param(1, $kid);
# execute
$sth->execute() or return undef;
# get the row
my $row = $sth->fetchrow_hashref();
# return the id
return (defined($row)) ? $row : undef;
}
##
# Gets a list of kwotes
sub list_kwotes {
my ($dbh, $sort_by, $order_direction, $return_amt,
$start_index, $search_string) = @_;
# clean up the numbers
$return_amt =~ s/[^0-9]//ig;
$start_index =~ s/[^0-9]//ig;
# ensure these numbers are ok
if ($start_index eq "" || int($start_index)<=0) {
$start_index = 0;
}
if ($return_amt eq "" || int($return_amt)<=0
|| int($return_amt) > 200) {
$return_amt = 20;
}
# break out the keywords
my @kws = split(/,/,$search_string);
# build SQL query
my $sql = "SELECT * FROM kwote ";
# search stuff
if (defined($search_string)) {
$sql .= "WHERE 1=1 ";
foreach my $kw (@kws) {
$sql.= "AND content LIKE ? ";
}
}
# sorting and paging
if (defined($sort_by)) {
$sql .= "ORDER BY $sort_by $order_direction ";
}
# paging
$sql .= "LIMIT $start_index, $return_amt ";
# prepare
my $sth = $dbh->prepare($sql) or return undef;
# apply the search criteria
if (defined($search_string)) {
for ($i=0; $i<@kws; $i++) {
$sth->bind_param($i+1, "\%".$kws[$i]."\%");
}
}
# execute
$sth->execute() or return undef;
# get the rows
my @rows;
while (my $row = $sth->fetchrow_hashref()) {
push(@rows, $row);
}
# return it
return @rows;
}
##
# Connect to the database
sub get_db_connection {
if (!$GLOBAL_DBH) {
$GLOBAL_DBH = DBI->connect(
"dbi:".DB_TYPE.":".DB_NAME.":".DB_HOST,
DB_USER,
DB_PASS
);
}
return $GLOBAL_DBH;
}
##
# Escape html
sub html_escape {
my ($data) = @_;
$data =~ s/</&lt;/g;
$data =~ s/>/&gt;/g;
$data =~ s/\n/<br \/>/g;
$data =~ s/"/&quot;/g;
return $data;
}
##
# Sends the HTML header
sub send_html_header {
print STDOUT "Content-type: text/html\n\n";
}
##
# Renders an HTML template
sub wrap_template {
my ($template_file, %vars) = @_;
open(IN,"$template_file");
my $data = join("",<IN>);
close(IN);
foreach $key (keys %vars) {
$data =~ s/\${$key}/$vars{$key}/ig;
}
return $data;
}
##
# Wraps and renders a template
sub render_template {
my ($template_file, %vars) = @_;
my $data = wrap_template($template_file, %vars);
print STDOUT $data;
}
##
# Parse form data
sub parse_form {
my (@pairs, $pair, $buffer, %FORM);
if ($ENV{'REQUEST_METHOD'} eq 'GET') {
@pairs = split(/&/, $ENV{'QUERY_STRING'});
} elsif ($ENV{'REQUEST_METHOD'} eq 'POST') {
read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
@pairs = split(/&/, $buffer);
}
foreach $pair (@pairs) {
local($name, $value) = split(/=/, $pair);
$name =~ tr/+/ /;
$name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$FORM{$name} = $value;
}
return %FORM;
}
1;

38
kwotes.conf.pl Executable file
View File

@ -0,0 +1,38 @@
#!/usr/bin/perl
use constant {
DB_TYPE => "mysql", # dbi database type (only MySQL is
# supported currently, due to the
# fact that "LIMIT X,X" is used
DB_NAME => "kwotes", # database name
DB_HOST => "localhost", # database host
DB_USER => "kwotes", # database user
DB_PASS => "kw0tes", # database password
MAX_VOTES_PER_IP => 4, # maximum votes per ip address per
# VOTE_TTL seconds.
MAX_KWOTES_PER_IP => 5, # maximum kwotes allowed per ip
# in SECS_BETWEEN_KWOTES
SECS_BETWEEN_KWOTES => 60*60, # seconds a user must wait after
# submitting MAX_KWOTES_PER_IP
# kwotes to the system before they
# are allowed to submit another
# kwote
NEGATIVE_KWOTE_TTL => (60*60)*24, # seconds before a negative rated
# quote is moved to the kwote
# backup table and deleted
VOTE_TTL => (60*60)*24 # seconds a vot log lasts, the vote
# log is the mechanism that keeps
# people from voting over and over
};
1;

208
kwotes.pl Executable file
View File

@ -0,0 +1,208 @@
#!/usr/bin/perl
###################################################
# -*- kwotes.pl -*- #
###################################################
# TOD: Put some interesting shit here, perhaps #
# something about kwotes being GPL #
###################################################
# bring in the config
require "kwotes.conf.pl";
# bring in the required libs
require "kwotes-lib.pl";
# is this getting called by the "delete" cronjob?
if ($ARGV[0] eq "cleanup") {
exit cleanup();
}
# parse the form data
my %FORM = parse_form();
# some vars
my $action = $FORM{"action"};
my $main_content;
my %vars;
# populate %vars with ENV
foreach my $key (keys %ENV) {
$vars{$key} = $ENV{$key};
}
# send the HTML header
send_html_header();
# add information that is displayed on every page
$vars{KWOTE_COUNT} = get_kwote_count();
$vars{KWOTE_BACKUP_COUNT} = get_kwote_backup_count();
############
# action: add (show add form)
if ($action eq "add") {
$vars{TITLE} = "Add Kwote";
$main_content = wrap_template("html/content-addform.html", %vars);
############
# action: doadd (add the kwote to the db)
} elsif ($action eq "doadd") {
if ($FORM{"content"} eq "") {
$vars{TITLE} = "An Error Occured";
$vars{ERROR_MESSAGE} = "No text entered";
$main_content = wrap_template("html/content-error.html",%vars);
} else {
# add the kwote
my $dbh = get_db_connection();
my $kid = add_kwote($dbh, $FORM{"content"}, $ENV{"REMOTE_ADDR"});
# wtf? errors? in my code? noooo.
if (!defined($kid)) {
$vars{TITLE} = "An Error Occured";
$vars{ERROR_MESSAGE} = "Couldn't add kwote";
$main_content = wrap_template("html/content-error.html",%vars);
# all was good
} else {
$vars{TITLE} = "Kwote Added";
$vars{KWOTE_ID} = $kid;
$main_content = wrap_template("html/content-addform-thanks.html", %vars);
}
}
##########
# action: show
} elsif ($action eq "show") {
# get the kwote
my $dbh = get_db_connection();
my $kwote = get_kwote($dbh, $FORM{"id"});
# wtf? errors? in my code? noooo.
if (!defined($kwote)) {
$vars{TITLE} = "Kwote Does Not Exist";
$vars{ERROR_MESSAGE} = "That kwote does not exist";
$main_content = wrap_template("html/content-error.html",%vars);
# all was good
} else {
$vars{TITLE} = "Kwote \#$kwote->{'id'}";
$vars{KWOTE_ID} = $kwote->{'id'};
$vars{KWOTE_TEXT} = html_escape($kwote->{'content'});
$vars{KWOTE_RATING} = $kwote->{'rating'};
$main_content = wrap_template("html/content-show-kwote.html", %vars);
}
##########
# action: latest
} elsif ($action eq "list") {
# what are we sorting on
my $sort = ($FORM{"o"} eq "date") ?
"submit_dt" : ( ($FORM{"o"} eq "rating") ? "rating" : undef);
# get start index
my $start_index = (defined($FORM{"s"})) ? $FORM{"s"} : 0;
# get max amount
my $max_returned = (defined($FORM{"m"})) ? $FORM{"m"} : 20;
# what is the "max records" we'll consider?
my $max_records = (defined($FORM{"mr"})) ? $FORM{"mr"} : 9999999999;
# what is the "sort order"
my $sort_order = ($FORM{"so"} eq "reverse") ? "ASC" : "DESC";
# search string?
my $search_string = $FORM{"ss"};
# get the kwote
my $dbh = get_db_connection();
my @rows = list_kwotes($dbh, $sort, $sort_order, $max_returned, $start_index, $search_string);
# setup these vars
$vars{TITLE} = "Kwotes";
$vars{ORDER} = $FORM{"o"};
$vars{NEXT_INDEX} = $start_index+$max_returned;
$vars{MAX_RETURN} = $max_returned;
$vars{LAST_INDEX} = $start_index-$max_returned;
$vars{MAX_RECORDS} = $max_records;
# add the search header if it was a search
if (defined($search_string)) {
$main_content .= wrap_template("html/content-search.html", %vars);
}
# get the navigation template
my $navigation_template = undef;
# forward, no back
if ($start_index<=0 && @rows>=$max_returned
&& ($start_index+$max_returned)<$max_records) {
$navigation_template = "html/content-list-navigate-no-back.html";
# forward and back
} elsif ($start_index>0 && @rows>=$max_returned
&& ($start_index+$max_returned)<$max_records ) {
$navigation_template = "html/content-list-navigate.html";
# back only
} elsif ($start_index>0 && @rows<$max_returned) {
$navigation_template = "html/content-list-navigate-no-forward.html";
}
# wrap the navigation template
$main_content .= wrap_template($navigation_template, %vars);
# loop through the results
if (defined(@rows)) {
for (my $i=0; $i<@rows && ($i+$start_index)<$max_records; $i++) {
my $row = $rows[$i];
$vars{KWOTE_ID} = $row->{'id'};
$vars{KWOTE_TEXT} = html_escape($row->{'content'});
$vars{KWOTE_RATING} = $row->{'rating'};
$main_content .= wrap_template("html/content-show-kwote.html", %vars);
}
}
# wrap the navigation template
$main_content .= wrap_template($navigation_template, %vars);
##########
# action: search (show the search page)
} elsif ($action eq "search") {
$vars{TITLE} = "Search";
$main_content = wrap_template("html/content-search.html", %vars);
##########
# action: love
} elsif ($action eq "love") {
vote($ENV{"REMOTE_ADDR"}, $FORM{"kid"}, "1");
$vars{TITLE} = "Love";
$main_content = "Vote Counted";
##########
# action: hate
} elsif ($action eq "hate") {
vote($ENV{"REMOTE_ADDR"}, $FORM{"kid"}, "-1");
$vars{TITLE} = "Hate";
$main_content = "Vote Counted";
##########
# show the homepage
} else {
$vars{TITLE} = "The Better kwote Database";
$main_content = wrap_template("html/content-default.html", %vars);
}
# finish the HTML
render_template("html/header.html", %vars);
print STDOUT $main_content;
render_template("html/footer.html", %vars);

28
kwotes.sql Executable file
View File

@ -0,0 +1,28 @@
CREATE TABLE kwote (
id INTEGER AUTO_INCREMENT,
submit_dt DATETIME,
content TEXT,
rating INT,
ip_address VARCHAR(15),
PRIMARY KEY(id)
);
CREATE TABLE kwote_backup (
id INTEGER AUTO_INCREMENT,
submit_dt DATETIME,
content TEXT,
rating INT,
ip_address VARCHAR(15),
PRIMARY KEY(id)
);
CREATE TABLE vote (
id INT AUTO_INCREMENT,
ip_address VARCHAR(15),
kwote_id INT,
vote_dt DATETIME,
PRIMARY KEY(id),
FOREIGN KEY(kwote_id) REFERENCES kwote(id)
);