diff --git a/Gemfile b/Gemfile index 4965d809b..6fc434b28 100644 --- a/Gemfile +++ b/Gemfile @@ -1,23 +1,13 @@ # Edit this Gemfile to bundle your application's dependencies. source 'http://gemcutter.org' - -## Bundle edge rails: -path "/Users/ayi/Projects/personal/rails3", :glob => "{*/,}*.gemspec" gem "rails", "3.0.0.beta" +gem "pg" +gem "memcache-client", :require => "memcache" -# ActiveRecord requires a database adapter. By default, -# Rails has selected sqlite3. -gem "sqlite3-ruby", :require => "sqlite3" - -## Bundle the gems you use: -# gem "bj" -# gem "hpricot", "0.6" -# gem "sqlite3-ruby", :require => "sqlite3" -# gem "aws-s3", :require => "aws/s3" - -## Bundle gems used only in certain environments: -# gem "rspec", :group => :test -# group :test do -# gem "webrat" -# end +group :test do + gem "shoulda" + gem "factory_girl" + gem "mocha" + gem "faker" +end diff --git a/INSTALL.debian b/INSTALL.debian new file mode 100644 index 000000000..6bf1c8cd5 --- /dev/null +++ b/INSTALL.debian @@ -0,0 +1,132 @@ +#!/bin/bash + +echo "This script is out of date; please read the INSTALL document" +exit 1 + +if [ $USER != root ] ; then + echo "You must run this script as root" + exit 1 +fi + +echo "Danbooru Install" +echo "This script will install Ruby, Rails, PostgreSQL, and Nginx. By the end," +echo "you should be able to connect to the server and create an account." +echo +echo "It will create a new user called danbooru which will run the Danbooru" +echo "processes. It will download the latest trunk copy and install it in" +echo "/var/www/danbooru. It will run three Mongrel processes, starting on port" +echo "8050." +echo +echo -n "Enter the hostname for this server (ex: danbooru.donmai.us): " +read hostname + +if [ -z $hostname ] ; then + echo "Must enter a hostname" + exit 1 +fi + +echo -n "Enter a name for the site (default: Danbooru): " +read sitename + +if [ -z $sitename ] ; then + sitename=Danbooru +fi + +# Install packages +apt-get -y install sudo gcc g++ make libreadline5-dev zlib1g-dev flex bison libgd2-noxpm libgd2-noxpm-dev bzip2 postgresql-8.3 postgresql-contrib-8.3 libpq-dev ruby ruby1.8-dev ri irb rdoc rubygems ragel memcached libmemcache-dev subversion nginx rake libopenssl-ruby mongrel + +# Install Ruby gems +for i in postgres diff-lcs html5 mongrel mongrel_cluster memcache-client aws-s3 json ; do gem install $i ; done +gem install rails --version=2.1.0 +gem install acts_as_versioned + +# Create user account +useradd -m danbooru +PG_HBA_FILE="/etc/postgresql/8.3/main/pg_hba.conf" +echo "local all postgres,danbooru trust" > $PG_HBA_FILE +echo "host all postgres,danbooru 127.0.0.1/32 trust" >> $PG_HBA_FILE +/etc/init.d/postgresql-8.3 restart + +# Install Danbooru +cd /var/www +svn export svn://donmai.us/danbooru/trunk danbooru +chown -R danbooru:danbooru danbooru +cd danbooru +mkdir -p public/data/sample +cd config +cp database.yml.example database.yml +cp local_config.rb.example local_config.rb +sed -i -e "s/DAN_HOSTNAME/$hostname/g" local_config.rb +sed -i -e "s/DAN_SITENAME/$sitename/g" local_config.rb +echo "--- " > mongrel_cluster.yml +echo "cwd: /var/www/danbooru" >> mongrel_cluster.yml +echo "port: \"8050\"" >> mongrel_cluster.yml +echo "environment: production" >> mongrel_cluster.yml +echo "address: 127.0.0.1" >> mongrel_cluster.yml +echo "servers: 3" >> mongrel_cluster.yml +echo "num_processors: 10" >> mongrel_cluster.yml +cd ../lib/danbooru_image_resizer +ruby extconf.rb +make +cd ../.. +sudo -u postgres createuser -s danbooru +sudo -u danbooru createdb danbooru +sudo -u danbooru psql danbooru < db/postgres.sql +sudo -u danbooru rake db:migrate RAILS_ENV=production +script/donmai/upbooru + +# Set up nginx +DANBOORU_CONF_FILE="/etc/nginx/sites-enabled/danbooru.conf" +echo "upstream mongrel {" > $DANBOORU_CONF_FILE +echo " server 127.0.0.1:8050;" >> $DANBOORU_CONF_FILE +echo " server 127.0.0.1:8051;" >> $DANBOORU_CONF_FILE +echo " server 127.0.0.1:8052;" >> $DANBOORU_CONF_FILE +echo "}" >> $DANBOORU_CONF_FILE +echo "server {" >> $DANBOORU_CONF_FILE +echo " listen 80;" >> $DANBOORU_CONF_FILE +echo " server_name $hostname;" >> $DANBOORU_CONF_FILE +echo " root /var/www/danbooru/public;" >> $DANBOORU_CONF_FILE +echo " index index.html;" >> $DANBOORU_CONF_FILE +echo " access_log /var/www/danbooru/log/server.access.log;" >> $DANBOORU_CONF_FILE +echo " error_log /var/www/danbooru/log/server.error.log;" >> $DANBOORU_CONF_FILE +echo " client_max_body_size 30m;" >> $DANBOORU_CONF_FILE +echo " location /stylesheets {" >> $DANBOORU_CONF_FILE +echo " expires max;" >> $DANBOORU_CONF_FILE +echo " break;" >> $DANBOORU_CONF_FILE +echo " }" >> $DANBOORU_CONF_FILE +echo " location /javascripts {" >> $DANBOORU_CONF_FILE +echo " expires max;" >> $DANBOORU_CONF_FILE +echo " break;" >> $DANBOORU_CONF_FILE +echo " }" >> $DANBOORU_CONF_FILE +echo " location /data {" >> $DANBOORU_CONF_FILE +echo " valid_referers none $hostname;" >> $DANBOORU_CONF_FILE +echo " if (\$invalid_referer) {" >> $DANBOORU_CONF_FILE +echo " return 403;" >> $DANBOORU_CONF_FILE +echo " }" >> $DANBOORU_CONF_FILE +echo " expires max;" >> $DANBOORU_CONF_FILE +echo " break;" >> $DANBOORU_CONF_FILE +echo " }" >> $DANBOORU_CONF_FILE +echo " location /maintenance.html {" >> $DANBOORU_CONF_FILE +echo " expires 10;" >> $DANBOORU_CONF_FILE +echo " }" >> $DANBOORU_CONF_FILE +echo " if (-f \$document_root/maintenance.html) {" >> $DANBOORU_CONF_FILE +echo " rewrite ^(.*)\$ /maintenance.html last;" >> $DANBOORU_CONF_FILE +echo " break;" >> $DANBOORU_CONF_FILE +echo " }" >> $DANBOORU_CONF_FILE +echo " location / {" >> $DANBOORU_CONF_FILE +echo " proxy_set_header X-Real-IP \$remote_addr;" >> $DANBOORU_CONF_FILE +echo " proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;" >> $DANBOORU_CONF_FILE +echo " proxy_set_header Host \$http_host;" >> $DANBOORU_CONF_FILE +echo " proxy_redirect false;" >> $DANBOORU_CONF_FILE +echo " if (!-f \$request_filename) {" >> $DANBOORU_CONF_FILE +echo " proxy_pass http://mongrel;" >> $DANBOORU_CONF_FILE +echo " }" >> $DANBOORU_CONF_FILE +echo " }" >> $DANBOORU_CONF_FILE +echo " error_page 404 /404.html;" >> $DANBOORU_CONF_FILE +echo " error_page 500 502 503 504 /500.html;" >> $DANBOORU_CONF_FILE +echo "}" >> $DANBOORU_CONF_FILE +/etc/init.d/nginx restart + +echo +echo "I'm done!" +echo "You should probably set the password for the danbooru account (run passwd danbooru)." diff --git a/INSTALL.freebsd b/INSTALL.freebsd new file mode 100644 index 000000000..44bb59039 --- /dev/null +++ b/INSTALL.freebsd @@ -0,0 +1,138 @@ +Danbooru Installation guide on FreeBSD (6.2) +Provided by Shuugo +Cleanup of dovac's guide: http://uruchai.com/2007/12/24/in-depth-guide-on-installing-danbooru-on-freebsd + +A minimal FreeBSD profile installation is encouraged. You can use VMWare to test how things work before trying on an actual machine. + +1)) Updating ports: +You can find how fully work here: http://www.freebsd.org/doc/en_US.ISO8859-1/books/handbook/ports-using.html but basically you will only need: + +1. portsnap fetch +2. portsnap extract + +2)) Install the following things through ports + +1. cd /usr/ports/devel/mkmf/ && make install clean +2. cd /usr/ports/ftp/wget/ && make install clean +3. cd /usr/ports/editors/nano/ && make install clean +4. cd /usr/ports/devel/subversion/ && make install clean (May ask for configuration: Choose "libiconv", "python25", enable "huge stack size", and disable "ipv6" +5. cd /usr/ports/databases/memcached/ && make install clean +6. cd /usr/ports/graphics/gd/ && make install clean (Make sure you enable iconv support) +7. cd /usr/ports/databases/postgresql83-client/ && make install clean (config defaults are ok) +8. cd /usr/ports/databases/postgresql83-server/ && make install clean (config defaults are ok) + +3)) Editing rc.conf for starting postgres and memcached at boot. + +1. nano /etc/rc.conf +2. Add the following at the end of the file: + +postgresql_enable="YES" +memcached_enable="yes" +memacached_flags="-l 127.0.0.1 -d m 100" + +3. Press Control + X to save. + +4. We run "/usr/local/etc/rc.d/postgresql initdb" to get ready to initialize postgres for first time. + +5. Use "/usr/local/etc/rc.d/postgresql start", to start the postgresql server. + +4)) Ruby on rails installation + +1. cd /usr/ports/lang/ruby18/ && make install clean (Disable ipv6) +2. rubygem-rails port is broken, download it from rubyforge +2a wget http://rubyforge.org/frs/download.php/29548/rubygems-1.0.1.tgz +2b tar xvf rubygems-1.0.1.tgz +2c cd rubygems-1.0.1 +2d ruby setup.rb +3. cd /usr/ports/www/rubygem-rails/ && make install clean (Be sure it's rubygem-rails-1.2.6. Rails 2.0 isn't compatible yet) +4. cd /usr/ports/www/rubygem-redcloth/ && make install clean +5. cd /usr/ports/www/rubygem-mongrel/ && make install clean + +Some gems are not available through ports + +6a wget http://rubyforge.org/frs/download.php/29624/postgres-0.7.9.2007.12.22.gem +6b /usr/bin/gem18 install --no-ri --install-dir /usr/local/lib/ruby/gems/1.8 ./postgres-0.7.9.2007.12.22.gem (There's a chance that it's not gem18, but gem I'll assume it's the correct path, if not, replace it on the following steps too) + +7. /usr/bin/gem18 aws-s3 diff-lcs acts_as_versioned html5 (Accept all dependencies) + +5)) Danbooru installation + +1. Create the danbooru user, -> adduser (danbooru, and choose a password) +2. cd /home/danbooru +3. svn co svn://danbooru.donmai.us/danbooru/trunk (This will get you the latest trunk) +4. cd trunk/ +5. mv * .. +6. cd .. +7. rm -r trunk/ + +8. Database creation: +8a su pgsql +8b CREATE DATABASE danbooru; +8c \q +8d psql -d danbooru -f /home/danbooru/db/postgres.sql +8e exit + +6)) Danbooru configuration + +1. cd /home/danbooru/config +2. mv database.yml.example database.yml +3. nano database.yml +3a Change all users to pgsql (Control+X to save and exit) +4. mv local_config.rb.example local_config.rb +5. nano local_config.rb +NOTE: You can check for additional configuration parameters and a brief explanation in default_config.rb +5a. Change the parameters needed an add these parameters +CONFIG["password_salt"] = “choujin-steinerr” (replace the salt with something else) +CONFIG["image_store"] = :local_flat (replace with :local_hierarchy if you want scaled folders for example /01/23/fullhash.ext, leave :local_falt to leave all the images in one same folder) +CONFIG["enable_caching"] = false (change to true to enable memcache, this will speed up things) +CONFIG["memcache_servers"] = ["localhost:4000"] (change to "localhost:11211") +5b Control + X to save and exit. + +6. Compiling the resizer +6a You can rather choose between using "ruby extconf.rb" or "ruby extconf.rb --with-gd-dir=/usr/local/gd", if you are using this last option you can ignore the 6b 6c and 6d steps. Using the first method will give you always the same gd version while the second command, the gd version will be updated when you update it through ports. +6b cp /usr/local/include/gd.h gd.h +6c cp /usr/local/include/gd_io.h gd_io.h +6d cp /usr/local/include/gdfx.h gdfx.h +6e make +6f make install + +7. cd /home/danbooru +7b /usr/local/bin/rake db:migrate + +7)) Basic webserver + +- Now you can run "mongrel_rails start", this will make danbooru public on your hostname:3000. You will see debug information in the console + +- For running it with mongrel and serving it "normally" you can use "mongrel_rails start -p 80 -d" This will tell mongrel to run on port 80 and as a daemon (not displaying anything in the console) + +8)) Advanced webserver + +Is encouraged that you rather configure Apache or lighttpd to proxy (or vhosting) the rails app. However this last method requries advanced configuration files and server modules. + +If you plan only on running danbooru here's a good configuration for lighttpd usage (you can install it through ports) +Mongrel running as a daemon (4 threads) You might want to add this to the rc.conf if you want to start it automatically + +1. cd /home/danbooru +2a mongrel_rails start -d -p 8001 -e production -P log/mongrel-1.pid +2b mongrel_rails start -d -p 8002 -e production -P log/mongrel-2.pid +2c mongrel_rails start -d -p 8003 -e production -P log/mongrel-3.pid +2d mongrel_rails start -d -p 8004 -e production -P log/mongrel-4.pid + +Then on lighttpd.conf be sure to have the following mods enabled (Remove # before its names if not): +-mod_rewrite +-mod_redirect +-mod_access +-mod_accesslog +-mod_compress +-mod_proxy + +3. Add this to your lighttpd.conf: + +proxy.balance = "fair" +proxy.server = ( "/" => + ( ( "host" => "127.0.0.1", "port" => 8001 ), + ( "host" => "127.0.0.1", "port" => 8002 ), + ( "host" => "127.0.0.1", "port" => 8003 ), + ( "host" => "127.0.0.1", "port" => 8004 ) ) ) + +4. Restart lighttpd: "/usr/local/etc/rc.d/lighttpd restart" \ No newline at end of file diff --git a/README b/README index 37ec8ea21..6653551d9 100644 --- a/README +++ b/README @@ -1,243 +1,55 @@ -== Welcome to Rails +=== Installation -Rails is a web-application framework that includes everything needed to create -database-backed web applications according to the Model-View-Control pattern. +It is recommended that you install Danbooru on a Debian-based system (Lenny or newer) since most of the required packages are available on APT. Although Danbooru has been successfully installed on Fedora, CentOS, FreeBSD, and OS X, the following instructions will assume you're installing on Debian. The Debian install script is straightforward and should be simple to adapt for other platforms. Install docs for other platforms are provided, but these are user contributed and may not be up to date. If you want something similar to Danbooru that is easier to install, try Shimmie (http://trac.shishnet.org/shimmie2). Shimmie uses PHP and MySQL and should be straightforward to install on most hosts. -This pattern splits the view (also called the presentation) into "dumb" templates -that are primarily responsible for inserting pre-built data in between HTML tags. -The model contains the "smart" domain objects (such as Account, Product, Person, -Post) that holds all the business logic and knows how to persist themselves to -a database. The controller handles the incoming requests (such as Save New Account, -Update Product, Show Post) by manipulating the model and directing data to the view. +For best performance, you will need at least 256MB of RAM for PostgreSQL and Rails. The memory requirement will grow as your database gets bigger; the main Danbooru database takes up around 1GB of memory by itself. -In Rails, the model is handled by what's called an object-relational mapping -layer entitled Active Record. This layer allows you to present the data from -database rows as objects and embellish these data objects with business logic -methods. You can read more about Active Record in -link:files/vendor/rails/activerecord/README.html. +- Danbooru has the following general dependencies: gcc, g++, make, readline, zlib, flex, bison, gd2, bzip2, postgresql-8.4, postgresql-contrib-8.4, ruby, rubygems, memcached, subversion, apache, and phusion passenger. Use your operating system's package management system whenever possible. This will simplify the process of installing init scripts, which will not always happen when compiling from source. +- Please read the section below about PostgreSQL and test_parser before proceeding. +- Danbooru has the following Ruby gem dependencies: pg, diff-lcs, html5, memcache-client, aws-s3, json, rails (version 3.0) + - You may need to specify the path to your PostgreSQL libraries and includes when building the postgres gem. The general format for this is: "gem install postgres -- --with-pgsql-dir=/usr/local/pgsql". Experiment with the other configure settings if this doesn't work. +- It's recommended you create a dedicated account for running the Danbooru database and/or web processes. If you go this route: + - Use the createuser command while logged in as postgres to grant database access to the danbooru account. + - You will need to update the pg_hba.conf file to grant your danbooru account trusted localhost access. Make sure to restart the database server (/etc/init.d/postgresql-8.3 restart) after making any changes. +- You now have to check out the Danbooru source code. It's recommended you create it in the /var/www directory, but you can put the code anywhere. + - To export from Subversion: "svn export svn://donmai.us/danbooru/trunk danbooru" + - Recursively change the owner of this directory to the danbooru account: "chown -R danbooru:danbooru danbooru" + - Create a public/data/sample directory. + - Compile the resizer at lib/danbooru_image_resizer: "ruby extconf.rb && make". Do not make install it. If this fails you will need to figure out your gd2/libjpeg/libpng dependencies. + - Create new database.yml and local_config.rb files in the config directory. Example files are provided. + - Create the database: "createdb danbooru" + - Load the schema: "psql danbooru < db/postgres.sql" + - Run the migrations: "RAILS_ENV=production rake db:migrate" + - Start the job daemon: "RAILS_ENV=production app/daemons/job_task_processor_ctl.rb start" + - You now need a way of managing the Rails process. The preferred method is using the Phusion Passenger module (see section below). Alternatively you can use Mongrel or fastcgi, there are several examples on the web. +- You should now be able to connect to your Danbooru instance. The first account you create will automatically become the administrator, so you should do this first. -The controller and view are handled by the Action Pack, which handles both -layers by its two parts: Action View and Action Controller. These two layers -are bundled in a single package due to their heavy interdependence. This is -unlike the relationship between the Active Record and Action Pack that is much -more separate. Each of these packages can be used independently outside of -Rails. You can read more about Action Pack in -link:files/vendor/rails/actionpack/README.html. +=== PostgreSQL and test_parser +Starting with version 1.16, Danbooru relies on PostgreSQL's full text search feature to speed up tag queries. The gains are especially noticeable on tags with large post counts and for multi-tag joins. Unfortunately in order to adapt it for Danbooru a custom parser is required. -== Getting Started +The easiest way of doing this on Debian is installing the the postgresql-contrib-8.4 package. You should do this prior to running the Danbooru database migrations. -1. At the command prompt, start a new Rails application using the rails command - and your application name. Ex: rails myapp -2. Change directory into myapp and start the web server: script/server (run with --help for options) -3. Go to http://localhost:3000/ and get "Welcome aboard: You're riding the Rails!" -4. Follow the guidelines to start developing your application +=== Apache and Phusion Passenger +Phusion Passenger is essentially mod_rails, a compiled module for Apache that is similar in functionality to fastcgi. It is used instead of fastcgi or Mongrel to proxy requests between Rails processes that Passenger manages. When used in conjunction with Ruby Enterprise Edition you can see improved performance and memory efficiency. Passenger also makes deployments much easier, requiring that you only touch a file called "restart.txt" in your tmp directory. -== Web Servers +Installing Passenger on Debian is relatively painless; you can follow the instructions here: http://www.modrails.com/install.html. Passenger will automatically detect Rails folders so the Apache configuration for your site will be basic; the Passenger website explains in detail. -By default, Rails will try to use Mongrel if it's are installed when started with script/server, otherwise Rails will use WEBrick, the webserver that ships with Ruby. But you can also use Rails -with a variety of other web servers. +=== Ruby Enterprise Edition -Mongrel is a Ruby-based webserver with a C component (which requires compilation) that is -suitable for development and deployment of Rails applications. If you have Ruby Gems installed, -getting up and running with mongrel is as easy as: gem install mongrel. -More info at: http://mongrel.rubyforge.org +REE is a special version of the Ruby interpreter that, among other things, uses a more intelligent malloc routine and performs copy-on-write garbage collection. The end result is better memory usage, up to 30% in ideal cases. -Say other Ruby web servers like Thin and Ebb or regular web servers like Apache or LiteSpeed or -Lighttpd or IIS. The Ruby web servers are run through Rack and the latter can either be setup to use -FCGI or proxy to a pack of Mongrels/Thin/Ebb servers. +It is fairly straightforward to install and won't override your existing Ruby installation. Find out more here: http://www.rubyenterpriseedition.com -== Apache .htaccess example for FCGI/CGI +=== Troubleshooting -# General Apache options -AddHandler fastcgi-script .fcgi -AddHandler cgi-script .cgi -Options +FollowSymLinks +ExecCGI +These instructions won't work for everyone. If your setup is not working, here are the steps I usually reccommend to people: -# If you don't want Rails to look in certain directories, -# use the following rewrite rules so that Apache won't rewrite certain requests -# -# Example: -# RewriteCond %{REQUEST_URI} ^/notrails.* -# RewriteRule .* - [L] +1) Test the database. Make sure you can connect to it using psql. Make sure the tables exist. If this fails, you need to work on correctly installing PostgreSQL, importing the initial schema, and running the migrations. -# Redirect all requests not available on the filesystem to Rails -# By default the cgi dispatcher is used which is very slow -# -# For better performance replace the dispatcher with the fastcgi one -# -# Example: -# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] -RewriteEngine On +2) Test the Rails database connection by using ruby script/console. Run Post.count to make sure Rails can connect to the database. If this fails, you need to make sure your Danbooru configuration files are correct. -# If your Rails application is accessed via an Alias directive, -# then you MUST also set the RewriteBase in this htaccess file. -# -# Example: -# Alias /myrailsapp /path/to/myrailsapp/public -# RewriteBase /myrailsapp +3) If you're using Mongrel, test connecting directly to the Mongrel process by running elinks http://localhost:PORT. If this fails, you need to debug your Mongrel configuration file. -RewriteRule ^$ index.html [QSA] -RewriteRule ^([^.]+)$ $1.html [QSA] -RewriteCond %{REQUEST_FILENAME} !-f -RewriteRule ^(.*)$ dispatch.cgi [QSA,L] - -# In case Rails experiences terminal errors -# Instead of displaying this message you can supply a file here which will be rendered instead -# -# Example: -# ErrorDocument 500 /500.html - -ErrorDocument 500 "

Application error

Rails application failed to start properly" - - -== Debugging Rails - -Sometimes your application goes wrong. Fortunately there are a lot of tools that -will help you debug it and get it back on the rails. - -First area to check is the application log files. Have "tail -f" commands running -on the server.log and development.log. Rails will automatically display debugging -and runtime information to these files. Debugging info will also be shown in the -browser on requests from 127.0.0.1. - -You can also log your own messages directly into the log file from your code using -the Ruby logger class from inside your controllers. Example: - - class WeblogController < ActionController::Base - def destroy - @weblog = Weblog.find(params[:id]) - @weblog.destroy - logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") - end - end - -The result will be a message in your log file along the lines of: - - Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1 - -More information on how to use the logger is at http://www.ruby-doc.org/core/ - -Also, Ruby documentation can be found at http://www.ruby-lang.org/ including: - -* The Learning Ruby (Pickaxe) Book: http://www.ruby-doc.org/docs/ProgrammingRuby/ -* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) - -These two online (and free) books will bring you up to speed on the Ruby language -and also on programming in general. - - -== Debugger - -Debugger support is available through the debugger command when you start your Mongrel or -Webrick server with --debugger. This means that you can break out of execution at any point -in the code, investigate and change the model, AND then resume execution! -You need to install ruby-debug to run the server in debugging mode. With gems, use 'gem install ruby-debug' -Example: - - class WeblogController < ActionController::Base - def index - @posts = Post.find(:all) - debugger - end - end - -So the controller will accept the action, run the first line, then present you -with a IRB prompt in the server window. Here you can do things like: - - >> @posts.inspect - => "[#nil, \"body\"=>nil, \"id\"=>\"1\"}>, - #\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]" - >> @posts.first.title = "hello from a debugger" - => "hello from a debugger" - -...and even better is that you can examine how your runtime objects actually work: - - >> f = @posts.first - => #nil, "body"=>nil, "id"=>"1"}> - >> f. - Display all 152 possibilities? (y or n) - -Finally, when you're ready to resume execution, you enter "cont" - - -== Console - -You can interact with the domain model by starting the console through script/console. -Here you'll have all parts of the application configured, just like it is when the -application is running. You can inspect domain models, change values, and save to the -database. Starting the script without arguments will launch it in the development environment. -Passing an argument will specify a different environment, like script/console production. - -To reload your controllers and models after launching the console run reload! - -== dbconsole - -You can go to the command line of your database directly through script/dbconsole. -You would be connected to the database with the credentials defined in database.yml. -Starting the script without arguments will connect you to the development database. Passing an -argument will connect you to a different database, like script/dbconsole production. -Currently works for mysql, postgresql and sqlite. - -== Description of Contents - -app - Holds all the code that's specific to this particular application. - -app/controllers - Holds controllers that should be named like weblogs_controller.rb for - automated URL mapping. All controllers should descend from ApplicationController - which itself descends from ActionController::Base. - -app/models - Holds models that should be named like post.rb. - Most models will descend from ActiveRecord::Base. - -app/views - Holds the template files for the view that should be named like - weblogs/index.html.erb for the WeblogsController#index action. All views use eRuby - syntax. - -app/views/layouts - Holds the template files for layouts to be used with views. This models the common - header/footer method of wrapping views. In your views, define a layout using the - layout :default and create a file named default.html.erb. Inside default.html.erb, - call <% yield %> to render the view using this layout. - -app/helpers - Holds view helpers that should be named like weblogs_helper.rb. These are generated - for you automatically when using script/generate for controllers. Helpers can be used to - wrap functionality for your views into methods. - -config - Configuration files for the Rails environment, the routing map, the database, and other dependencies. - -db - Contains the database schema in schema.rb. db/migrate contains all - the sequence of Migrations for your schema. - -doc - This directory is where your application documentation will be stored when generated - using rake doc:app - -lib - Application specific libraries. Basically, any kind of custom code that doesn't - belong under controllers, models, or helpers. This directory is in the load path. - -public - The directory available for the web server. Contains subdirectories for images, stylesheets, - and javascripts. Also contains the dispatchers and the default HTML files. This should be - set as the DOCUMENT_ROOT of your web server. - -script - Helper scripts for automation and generation. - -test - Unit and functional tests along with fixtures. When using the script/generate scripts, template - test files will be generated for you and placed in this directory. - -vendor - External libraries that the application depends on. Also includes the plugins subdirectory. - If the app has frozen rails, those gems also go here, under vendor/rails/. - This directory is in the load path. +4) Test Apache to make sure it's proxying requests correctly. If this fails, you need to debug your Apache configuration file. diff --git a/app/logical/anonymous_user.rb b/app/logical/anonymous_user.rb new file mode 100644 index 000000000..e69de29bb diff --git a/app/models/pending_post.rb b/app/models/pending_post.rb new file mode 100644 index 000000000..19fbaa604 --- /dev/null +++ b/app/models/pending_post.rb @@ -0,0 +1,2 @@ +class PendingPost < ActiveRecord::Base +end diff --git a/app/models/post.rb b/app/models/post.rb new file mode 100644 index 000000000..b31552f80 --- /dev/null +++ b/app/models/post.rb @@ -0,0 +1,390 @@ +class Post < ActiveRecord::Base + class Deletion < ActiveRecord::Base + set_table_name "deleted_posts" + end + + class Pending < ActiveRecord::Base + set_table_name "pending_posts" + + def process! + update_attribute(:status, "processing") + move_file + calculate_hash + calculate_dimensions + generate_resizes + convert_to_post + update_attribute(:status, "finished") + end + + def move_file + # Download the file + # Move the tempfile into the data store + # Distribute to other servers + end + + def calculate_hash + # Calculate the MD5 hash of the file + end + + def calculate_dimensions + # Calculate the dimensions of the image + end + + def generate_resizes + # Generate width=150 + # Generate width=1000 + # + end + + def convert_to_post + end + + private + def download_from_source + self.source = "" if source.nil? + + return if source !~ /^http:\/\// || !file_ext.blank? + + begin + Danbooru.http_get_streaming(source) do |response| + File.open(tempfile_path, "wb") do |out| + response.read_body do |block| + out.write(block) + end + end + end + + if source.to_s =~ /\/src\/\d{12,}|urnc\.yi\.org|yui\.cynthia\.bne\.jp/ + self.source = "Image board" + end + + return true + rescue SocketError, URI::Error, SystemCallError => x + delete_tempfile + errors.add "source", "couldn't be opened: #{x}" + return false + end + end + + def move_tempfile + end + + def distribute_file + end + + def generate_resize_for(width) + end + end + + class Version < ActiveRecord::Base + set_table_name "post_versions" + end + + module FileMethods + def self.included(m) + m.before_validation_on_create :download_source + m.before_validation_on_create :validate_tempfile_exists + m.before_validation_on_create :determine_content_type + m.before_validation_on_create :validate_content_type + m.before_validation_on_create :generate_hash + m.before_validation_on_create :set_image_dimensions + m.before_validation_on_create :generate_sample + m.before_validation_on_create :generate_preview + m.before_validation_on_create :move_file + end + + def validate_tempfile_exists + unless File.exists?(tempfile_path) + errors.add :file, "not found, try uploading again" + return false + end + end + + def validate_content_type + unless %w(jpg png gif swf).include?(file_ext.downcase) + errors.add(:file, "is an invalid content type: " + file_ext.downcase) + return false + end + end + + def file_name + md5 + "." + file_ext + end + + def delete_tempfile + FileUtils.rm_f(tempfile_path) + FileUtils.rm_f(tempfile_preview_path) + FileUtils.rm_f(tempfile_sample_path) + end + + def tempfile_path + "#{RAILS_ROOT}/public/data/#{$PROCESS_ID}.upload" + end + + def tempfile_preview_path + "#{RAILS_ROOT}/public/data/#{$PROCESS_ID}-preview.jpg" + end + + # def file_size + # File.size(file_path) rescue 0 + # end + + # Generate an MD5 hash for the file. + def generate_hash + unless File.exists?(tempfile_path) + errors.add(:file, "not found") + return false + end + + self.md5 = File.open(tempfile_path, 'rb') {|fp| Digest::MD5.hexdigest(fp.read)} + self.file_size = File.size(tempfile_path) + + if Post.exists?(["md5 = ?", md5]) + delete_tempfile + errors.add "md5", "already exists" + return false + else + return true + end + end + + def generate_preview + return true unless image? && width && height + + unless File.exists?(tempfile_path) + errors.add(:file, "not found") + return false + end + + size = Danbooru.reduce_to({:width=>width, :height=>height}, {:width=>150, :height=>150}) + + # Generate the preview from the new sample if we have one to save CPU, otherwise from the image. + if File.exists?(tempfile_sample_path) + path, ext = tempfile_sample_path, "jpg" + else + path, ext = tempfile_path, file_ext + end + + begin + Danbooru.resize(ext, path, tempfile_preview_path, size, 95) + rescue Exception => x + errors.add "preview", "couldn't be generated (#{x})" + return false + end + + return true + end + + # Automatically download from the source if it's a URL. + def download_source + self.source = "" if source.nil? + + return if source !~ /^http:\/\// || !file_ext.blank? + + begin + Danbooru.http_get_streaming(source) do |response| + File.open(tempfile_path, "wb") do |out| + response.read_body do |block| + out.write(block) + end + end + end + + if source.to_s =~ /\/src\/\d{12,}|urnc\.yi\.org|yui\.cynthia\.bne\.jp/ + self.source = "Image board" + end + + return true + rescue SocketError, URI::Error, SystemCallError => x + delete_tempfile + errors.add "source", "couldn't be opened: #{x}" + return false + end + end + + def determine_content_type + imgsize = ImageSize.new(File.open(tempfile_path, "rb")) + + unless imgsize.get_width.nil? + self.file_ext = imgsize.get_type.gsub(/JPEG/, "JPG").downcase + end + end + + # Assigns a CGI file to the post. This writes the file to disk and generates a unique file name. + def file=(f) + return if f.nil? || f.size == 0 + + self.file_ext = content_type_to_file_ext(f.content_type) || find_ext(f.original_filename) + + if f.local_path + # Large files are stored in the temp directory, so instead of + # reading/rewriting through Ruby, just rely on system calls to + # copy the file to danbooru's directory. + FileUtils.cp(f.local_path, tempfile_path) + else + File.open(tempfile_path, 'wb') {|nf| nf.write(f.read)} + end + end + + def set_image_dimensions + if image? or flash? + imgsize = ImageSize.new(File.open(tempfile_path, "rb")) + self.width = imgsize.get_width + self.height = imgsize.get_height + end + end + + # Returns true if the post is an image format that GD can handle. + def image? + %w(jpg jpeg gif png).include?(file_ext.downcase) + end + + # Returns true if the post is a Flash movie. + def flash? + file_ext == "swf" + end + + def find_ext(file_path) + ext = File.extname(file_path) + if ext.blank? + return "txt" + else + ext = ext[1..-1].downcase + ext = "jpg" if ext == "jpeg" + return ext + end + end + + def content_type_to_file_ext(content_type) + case content_type.chomp + when "image/jpeg" + return "jpg" + + when "image/gif" + return "gif" + + when "image/png" + return "png" + + when "application/x-shockwave-flash" + return "swf" + + else + nil + end + end + + def preview_dimensions + if image? + dim = Danbooru.reduce_to({:width => width, :height => height}, {:width => 150, :height => 150}) + return [dim[:width], dim[:height]] + else + return [150, 150] + end + end + + def tempfile_sample_path + "#{RAILS_ROOT}/public/data/#{$PROCESS_ID}-sample.jpg" + end + + def regenerate_sample + return false unless image? + + if generate_sample && File.exists?(tempfile_sample_path) + FileUtils.mkdir_p(File.dirname(sample_path), :mode => 0775) + FileUtils.mv(tempfile_sample_path, sample_path) + FileUtils.chmod(0775, sample_path) + puts "Fixed sample for #{id}" + return true + else + puts "Error generating sample for #{id}" + return false + end + end + + def generate_sample + return true unless image? + return true unless CONFIG["image_samples"] + return true unless (width && height) + return true if (file_ext.downcase == "gif") + + size = Danbooru.reduce_to({:width => width, :height => height}, {:width => CONFIG["sample_width"], :height => CONFIG["sample_height"]}, CONFIG["sample_ratio"]) + + # We can generate the sample image during upload or offline. Use tempfile_path + # if it exists, otherwise use file_path. + path = tempfile_path + path = file_path unless File.exists?(path) + unless File.exists?(path) + errors.add(:file, "not found") + return false + end + + # If we're not reducing the resolution for the sample image, only reencode if the + # source image is above the reencode threshold. Anything smaller won't be reduced + # enough by the reencode to bother, so don't reencode it and save disk space. + if size[:width] == width && size[:height] == height && File.size?(path) < CONFIG["sample_always_generate_size"] + return true + end + + # If we already have a sample image, and the parameters havn't changed, + # don't regenerate it. + if size[:width] == sample_width && size[:height] == sample_height + return true + end + + size = Danbooru.reduce_to({:width => width, :height => height}, {:width => CONFIG["sample_width"], :height => CONFIG["sample_height"]}) + begin + Danbooru.resize(file_ext, path, tempfile_sample_path, size, 90) + rescue Exception => x + errors.add "sample", "couldn't be created: #{x}" + return false + end + + self.sample_width = size[:width] + self.sample_height = size[:height] + return true + end + + # Returns true if the post has a sample image. + def has_sample? + sample_width.is_a?(Integer) + end + + # Returns true if the post has a sample image, and we're going to use it. + def use_sample?(user = nil) + if user && !user.show_samples? + false + else + CONFIG["image_samples"] && has_sample? + end + end + + def sample_url(user = nil) + if use_sample?(user) + store_sample_url + else + file_url + end + end + + def get_sample_width(user = nil) + if use_sample?(user) + sample_width + else + width + end + end + + def get_sample_height(user = nil) + if use_sample?(user) + sample_height + else + height + end + end + + def sample_percentage + 100 * get_sample_width.to_f / width + end + end +end diff --git a/app/models/tag.rb b/app/models/tag.rb new file mode 100644 index 000000000..972262cc1 --- /dev/null +++ b/app/models/tag.rb @@ -0,0 +1,2 @@ +class Tag < ActiveRecord::Base +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 000000000..3af5b661a --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,47 @@ +require 'digest/sha1' + +class User < ActiveRecord::Base + attr_accessor :password + + validates_length_of :name, :within => 2..20, :on => :create + validates_format_of :name, :with => /\A[^\s;,]+\Z/, :on => :create, :message => "cannot have whitespace, commas, or semicolons" + validates_uniqueness_of :name, :case_sensitive => false, :on => :create + validates_uniqueness_of :email, :case_sensitive => false, :on => :create, :if => lambda {|rec| !rec.email.blank?} + validates_confirmation_of :password + + before_save :encrypt_password + after_save {|rec| Cache.put("user_name:#{rec.id}", rec.name)} + + scope :named, lambda {|name| where(["lower(name) = ?", name])} + + def self.authenticate(name, pass) + authenticate_hash(name, sha1(pass)) + end + + def self.authenticate_hash(name, pass) + where(["lower(name) = ? AND password_hash = ?", name.downcase, pass]).first != nil + end + + def self.sha1(pass) + Digest::SHA1.hexdigest("#{Danbooru.config.password_salt}--#{pass}--") + end + + def self.find_name(user_id) + Cache.get("user_name:#{user_id}") do + select_value_sql("SELECT name FROM users WHERE id = ?", user_id) || Danbooru.config.default_guest_name + end + end + + def can_update?(object, foreign_key = :user_id) + is_moderator? || is_admin? || object.__send__(foreign_key) == id + end + + def pretty_name + name.tr("_", " ") + end + + def encrypt_password + self.password_hash = self.class.sha1(password) if password + end +end + diff --git a/app/presenters/tag_set_presenter.rb b/app/presenters/tag_set_presenter.rb new file mode 100644 index 000000000..ebf8a7f2f --- /dev/null +++ b/app/presenters/tag_set_presenter.rb @@ -0,0 +1,41 @@ +=begin rdoc + A tag set represents a set of tags that are displayed together. + This class makes it easy to fetch the categories for all the + tags in one call instead of fetching them sequentially. +=end + +class TagSetPresenter < Presenter + def initialize(source) + @category_cache = {} + end + + def to_list_html(template, options = {}) + ul_class_attribute = options[:ul_class] ? %{class="#{options[:ul_class]}"} : "" + ul_id_attribute = options[:ul_id] ? %{id="#{options[:ul_id]}"} : "" + + html = "" + html << "
    " + @tags.each do |tag| + html << build_list_item(tag, template, options) + end + html << "
" + html + end + +private + def fetch_categories(tags) + end + + def build_list_item(tag, template, options) + html = "" + html << "
  • " + + if options[:show_extra_links] + end + + humanized_tag = tag.tr("_", " ") + html << %{a href="/posts?tags=#{u(tag)}">#{h(humanized_tag)}} + html << "
  • " + html + end +end diff --git a/config.ru b/config.ru index 007061899..d201948e0 100644 --- a/config.ru +++ b/config.ru @@ -1,4 +1,4 @@ # This file is used by Rack-based servers to start the application. require ::File.expand_path('../config/environment', __FILE__) -run Danbooru2::Application +run Danbooru::Application diff --git a/config/application.rb b/config/application.rb index b7ba9d2d4..1943371e2 100644 --- a/config/application.rb +++ b/config/application.rb @@ -5,26 +5,15 @@ require 'rails/all' # Auto-require default libraries and those for the current Rails environment. Bundler.require :default, Rails.env -module Danbooru2 +module Danbooru class Application < Rails::Application - # Settings in config/environments/* take precedence over those specified here. - # Application configuration should go into files in config/initializers - # -- all .rb files in that directory are automatically loaded. - # Add additional load paths for your own custom dirs - # config.load_paths += %W( #{config.root}/extras ) + config.load_paths += %W( #{config.root}/presenters #{config.root}/logical ) # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named # config.plugins = [ :exception_notification, :ssl_requirement, :all ] - # Activate observers that should always be running - # config.active_record.observers = :cacher, :garbage_collector, :forum_observer - - # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. - # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. - # config.time_zone = 'Central Time (US & Canada)' - # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')] # config.i18n.default_locale = :de @@ -35,6 +24,8 @@ module Danbooru2 # g.template_engine :erb # g.test_framework :test_unit, :fixture => true # end + + config.active_record.schema_format = :sql # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters << :password diff --git a/config/danbooru_default_config.rb b/config/danbooru_default_config.rb new file mode 100644 index 000000000..71d3d8295 --- /dev/null +++ b/config/danbooru_default_config.rb @@ -0,0 +1,109 @@ +module Danbooru + class Configuration + # The version of this Danbooru. + def version + "2.0.0" + end + + # The name of this Danbooru. + def app_name + "Danbooru" + end + + # The default name to use for anyone who isn't logged in. + def default_guest_name + "Anonymous" + end + + # This is a salt used to make dictionary attacks on account passwords harder. + def password_salt + "choujin-steiner" + end + + # Set to true to allow new account signups. + def enable_signups? + true + end + + # Set to true to give all new users privileged access. + def start_as_privileged? + false + end + + # Set to true to give all new users contributor access. + def start_as_contributor? + false + end + + # What method to use to store images. + # local_flat: Store every image in one directory. + # local_hierarchy: Store every image in a hierarchical directory, based on the post's MD5 hash. On some file systems this may be faster. + def image_store + :local_flat + end + + # Thumbnail size + def small_image_width + 150 + end + + # Medium resize image width + def medium_image_width + 500 + end + + # Large resize image width + def large_image_width + 1024 + end + + # List of memcached servers + def memcached_servers + %w(localhost:11211) + end + + # After a post receives this many comments, new comments will no longer bump the post in comment/index. + def comment_threshold + 40 + end + + # Members cannot post more than X comments in an hour. + def member_comment_limit + 2 + end + + # Determine who can see a post. + def can_see_post?(post, user) + true + end + + # Determines who can see ads. + def can_see_ads?(user) + false + end + + # This is required for Rails 2.0. + def session_secret_key + "This should be at least 30 characters long" + end + + # Users cannot search for more than X regular tags at a time. + def tag_query_limit + 6 + end + + # Max number of posts to cache + def tag_subscription_post_limit + 200 + end + + # Max number of tag subscriptions per user + def max_tag_subscriptions + 5 + end + + def server_host + Socket.gethostname + end + end +end diff --git a/config/danbooru_local_config.rb b/config/danbooru_local_config.rb new file mode 100644 index 000000000..64bc3dced --- /dev/null +++ b/config/danbooru_local_config.rb @@ -0,0 +1,5 @@ +module Danbooru + class CustomConfiguration < Configuration + # Define your custom overloads here + end +end diff --git a/config/database.yml b/config/database.yml index 025d62a8d..3032947e7 100644 --- a/config/database.yml +++ b/config/database.yml @@ -1,8 +1,8 @@ # SQLite version 3.x # gem install sqlite3-ruby (not necessary on OS X Leopard) development: - adapter: sqlite3 - database: db/development.sqlite3 + adapter: postgresql + database: danbooru2 pool: 5 timeout: 5000 @@ -10,13 +10,13 @@ development: # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: - adapter: sqlite3 - database: db/test.sqlite3 + adapter: postgresql + database: danbooru2_test pool: 5 timeout: 5000 production: - adapter: sqlite3 - database: db/production.sqlite3 + adapter: postgresql + database: danbooru2 pool: 5 timeout: 5000 diff --git a/config/environment.rb b/config/environment.rb index 52ccf5eff..00be728e9 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -2,4 +2,4 @@ require File.expand_path('../application', __FILE__) # Initialize the rails application -Danbooru2::Application.initialize! +Danbooru::Application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb index 8cc40b83b..218bcc5c8 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,4 +1,4 @@ -Danbooru2::Application.configure do +Danbooru::Application.configure do # Settings specified here will take precedence over those in config/environment.rb # In the development environment your application's code is reloaded on diff --git a/config/environments/production.rb b/config/environments/production.rb index c178467cd..0f4178e0a 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,4 +1,4 @@ -Danbooru2::Application.configure do +Danboorus::Application.configure do # Settings specified here will take precedence over those in config/environment.rb # The production environment is meant for finished, "live" apps. diff --git a/config/environments/test.rb b/config/environments/test.rb index c084c63e9..c5bcc6328 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,4 +1,4 @@ -Danbooru2::Application.configure do +Danbooru::Application.configure do # Settings specified here will take precedence over those in config/environment.rb # The test environment is used exclusively to run your application's diff --git a/config/initializers/cookie_verification_secret.rb b/config/initializers/cookie_verification_secret.rb index e7c0b303b..71c8e22fb 100644 --- a/config/initializers/cookie_verification_secret.rb +++ b/config/initializers/cookie_verification_secret.rb @@ -4,4 +4,4 @@ # If you change this key, all old signed cookies will become invalid! # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. -ActionController::Base.cookie_verifier_secret = '9d3404d788351e80007345ca60b0cc4484e1906a2feab4be105e0637946c220e1f7239902b4234892923b9b45cf6f9a0a818302d81f890849e9bfe19ce03a30d' +ActionController::Base.cookie_verifier_secret = '214c98302eef905ab8bce4a19562e322097c526f28e718160a3c0d617ddc8edab6ae7e22cb5eec8930e215bfb936a7086d6f5b146c0092a9af1884613ce0a260' diff --git a/config/initializers/core_extensions.rb b/config/initializers/core_extensions.rb new file mode 100644 index 000000000..6e1dd9193 --- /dev/null +++ b/config/initializers/core_extensions.rb @@ -0,0 +1,31 @@ +class ActiveRecord::Base + class << self + public :sanitize_sql_array + end + + %w(execute select_value select_values select_all).each do |method_name| + define_method("#{method_name}_sql") do |sql, *params| + connection.__send__(method_name, self.class.sanitize_sql_array([sql, *params])) + end + + self.class.__send__(:define_method, "#{method_name}_sql") do |sql, *params| + connection.__send__(method_name, sanitize_sql_array([sql, *params])) + end + end +end + +class NilClass + def id + raise NoMethodError + end +end + +class String + def to_escaped_for_sql_like + return self.gsub(/\\/, '\0\0').gsub(/%/, '\\%').gsub(/_/, '\\_').gsub(/\*/, '%') + end + + def to_escaped_js + return self.gsub(/\\/, '\0\0').gsub(/['"]/) {|m| "\\#{m}"}.gsub(/\r\n|\r|\n/, '\\n') + end +end diff --git a/config/initializers/danbooru_config.rb b/config/initializers/danbooru_config.rb new file mode 100644 index 000000000..a11e5a360 --- /dev/null +++ b/config/initializers/danbooru_config.rb @@ -0,0 +1,10 @@ +require "#{Rails.root}/config/danbooru_default_config" +require "#{Rails.root}/config/danbooru_local_config" + +module Danbooru + def config + @configuration ||= CustomConfiguration.new + end + + module_function :config +end diff --git a/config/initializers/memcache.rb b/config/initializers/memcache.rb new file mode 100644 index 000000000..e91e9b097 --- /dev/null +++ b/config/initializers/memcache.rb @@ -0,0 +1,6 @@ +require 'memcache' + +unless defined?(MEMCACHE) + MEMCACHE = MemCache.new :c_threshold => 10_000, :compression => true, :debug => false, :namespace => Danbooru.config.app_name.gsub(/[^A-Za-z0-9]/, "_"), :readonly => false, :urlencode => false + MEMCACHE.servers = Danbooru.config.memcached_servers +end diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index c331cb025..4d42d0d05 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -5,8 +5,8 @@ # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. ActionController::Base.session = { - :key => '_danbooru2_session', - :secret => '8c46b9a05b0222b74454c6f4bd461be89cc762d9ef3dab4513997670ffed45086bc7a025d45ead5accca656cfb9ab64e70dd44c4379653cf8d4ca45f455ac8ec' + :key => '_danbooru_session', + :secret => '3102c705148af8124298f9e89d45da3d26e47cc4d9a67cb1c8d9c42c008ee253786346efda50331bb14811f1f445c1c9ed2d51597ad2017328de0dd263048d1a' } # Use the database for sessions instead of the cookie-based default, diff --git a/config/routes.rb b/config/routes.rb index 838962a77..de5a72e74 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,4 @@ -Danbooru2::Application.routes.draw do |map| +Danbooru::Application.routes.draw do |map| # The priority is based upon order of creation: # first created -> highest priority. diff --git a/db/development_structure.sql b/db/development_structure.sql new file mode 100644 index 000000000..20eab8d20 --- /dev/null +++ b/db/development_structure.sql @@ -0,0 +1,115 @@ +-- +-- PostgreSQL database dump +-- + +SET statement_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = off; +SET check_function_bodies = false; +SET client_min_messages = warning; +SET escape_string_warning = off; + +SET search_path = public, pg_catalog; + +SET default_tablespace = ''; + +SET default_with_oids = false; + +-- +-- Name: schema_migrations; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- + +CREATE TABLE schema_migrations ( + version character varying(255) NOT NULL +); + + +-- +-- Name: users; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- + +CREATE TABLE users ( + id integer NOT NULL, + created_at timestamp without time zone, + updated_at timestamp without time zone, + name character varying(255) NOT NULL, + password_hash character varying(255) NOT NULL, + email character varying(255), + invited_by integer, + is_banned boolean DEFAULT false NOT NULL, + is_privileged boolean DEFAULT false NOT NULL, + is_contributor boolean DEFAULT false NOT NULL, + is_janitor boolean DEFAULT false NOT NULL, + is_moderator boolean DEFAULT false NOT NULL, + is_admin boolean DEFAULT false NOT NULL, + last_logged_in_at timestamp without time zone, + last_forum_read_at timestamp without time zone, + has_mail boolean DEFAULT false NOT NULL, + receive_email_notifications boolean DEFAULT false NOT NULL, + comment_threshold integer DEFAULT (-1) NOT NULL, + always_resize_images boolean DEFAULT false NOT NULL, + favorite_tags text, + blacklisted_tags text +); + + +-- +-- Name: users_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE users_id_seq + START WITH 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; + + +-- +-- Name: users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE users_id_seq OWNED BY users.id; + + +-- +-- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE users ALTER COLUMN id SET DEFAULT nextval('users_id_seq'::regclass); + + +-- +-- Name: users_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- + +ALTER TABLE ONLY users + ADD CONSTRAINT users_pkey PRIMARY KEY (id); + + +-- +-- Name: index_users_on_email; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- + +CREATE UNIQUE INDEX index_users_on_email ON users USING btree (email); + + +-- +-- Name: index_users_on_name; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- + +CREATE UNIQUE INDEX index_users_on_name ON users USING btree (lower((name)::text)); + + +-- +-- Name: unique_schema_migrations; Type: INDEX; Schema: public; Owner: -; Tablespace: +-- + +CREATE UNIQUE INDEX unique_schema_migrations ON schema_migrations USING btree (version); + + +-- +-- PostgreSQL database dump complete +-- + +INSERT INTO schema_migrations (version) VALUES ('20100204211522'); \ No newline at end of file diff --git a/db/migrate/20100204211522_create_users.rb b/db/migrate/20100204211522_create_users.rb new file mode 100644 index 000000000..97b070041 --- /dev/null +++ b/db/migrate/20100204211522_create_users.rb @@ -0,0 +1,37 @@ +class CreateUsers < ActiveRecord::Migration + def self.up + create_table :users do |t| + t.timestamps + + t.column :name, :string, :null => false + t.column :password_hash, :string, :null => false + t.column :email, :string + t.column :invited_by, :integer + t.column :is_banned, :boolean, :null => false, :default => false + t.column :is_privileged, :boolean, :null => false, :default => false + t.column :is_contributor, :boolean, :null => false, :default => false + t.column :is_janitor, :boolean, :null => false, :default => false + t.column :is_moderator, :boolean, :null => false, :default => false + t.column :is_admin, :boolean, :null => false, :default => false + + # Cached data + t.column :last_logged_in_at, :datetime + t.column :last_forum_read_at, :datetime + t.column :has_mail, :boolean, :null => false, :default => false + + # Profile settings + t.column :receive_email_notifications, :boolean, :null => false, :default => false + t.column :comment_threshold, :integer, :null => false, :default => -1 + t.column :always_resize_images, :boolean, :null => false, :default => false + t.column :favorite_tags, :text + t.column :blacklisted_tags, :text + end + + execute "CREATE UNIQUE INDEX index_users_on_name ON users ((lower(name)))" + add_index :users, :email, :unique => true + end + + def self.down + drop_table :users + end +end diff --git a/lib/cache.rb b/lib/cache.rb new file mode 100644 index 000000000..e2c2a7dcf --- /dev/null +++ b/lib/cache.rb @@ -0,0 +1,92 @@ +module Cache + def expire(options = {}) + tags = options[:tags] + cache_version = Cache.get("$cache_version").to_i + + Cache.put("$cache_version", cache_version + 1) + + if tags + tags.scan(/\S+/).each do |x| + key = "tag:#{x}" + key_version = Cache.get(key).to_i + Cache.put(key, key_version + 1) + end + end + end + + def incr(key) + val = Cache.get(key) + Cache.put(key, val.to_i + 1) + ActiveRecord::Base.logger.debug('MemCache Incr %s' % [key]) + end + + def get(key, expiry = 0) + key.gsub!(/\s/, "_") + key = key[0, 200] + + if block_given? + return yield + else + return nil + end + + begin + start_time = Time.now + value = MEMCACHE.get key + elapsed = Time.now - start_time + ActiveRecord::Base.logger.debug('MemCache Get (%0.6f) %s' % [elapsed, key]) + if value.nil? and block_given? then + value = yield + MEMCACHE.set key, value, expiry + end + value + rescue MemCache::MemCacheError => err + ActiveRecord::Base.logger.debug "MemCache Error: #{err.message}" + if block_given? then + value = yield + put key, value, expiry + end + value + end + end + + def put(key, value, expiry = 0) + key.gsub!(/\s/, "_") + key = key[0, 200] + + begin + start_time = Time.now + MEMCACHE.set key, value, expiry + elapsed = Time.now - start_time + ActiveRecord::Base.logger.debug('MemCache Set (%0.6f) %s' % [elapsed, key]) + value + rescue MemCache::MemCacheError => err + ActiveRecord::Base.logger.debug "MemCache Error: #{err.message}" + nil + end + end + + def delete(key, delay = nil) + begin + start_time = Time.now + MEMCACHE.delete key, delay + elapsed = Time.now - start_time + ActiveRecord::Base.logger.debug('MemCache Delete (%0.6f) %s' % [elapsed, key]) + nil + rescue MemCache::MemCacheError => err + ActiveRecord::Base.logger.debug "MemCache Error: #{err.message}" + nil + end + end + + def sanitize_key(key) + key.gsub(/\W/, "_").slice(0, 220) + end + + module_function :get + module_function :expire + module_function :incr + module_function :put + module_function :delete + module_function :sanitize_key +end diff --git a/public/javascripts/rails.js b/public/javascripts/rails.js index 2a0a05d25..f7ddba390 100644 --- a/public/javascripts/rails.js +++ b/public/javascripts/rails.js @@ -1,4 +1,11 @@ document.observe("dom:loaded", function() { + var authToken = $$('meta[name=csrf-token]').first().readAttribute('content'), + authParam = $$('meta[name=csrf-param]').first().readAttribute('content'), + formTemplate = '
    \ + #{realmethod}\ +
    ', + realmethodTemplate = ''; + function handleRemote(element) { var method, url, params; @@ -34,20 +41,46 @@ document.observe("dom:loaded", function() { } $(document.body).observe("click", function(event) { + var message = event.element().readAttribute('data-confirm'); + if (message && !confirm(message)) { + event.stop(); + return false; + } + var element = event.findElement("a[data-remote=true]"); if (element) { handleRemote(element); event.stop(); } - }); - $(document.body).observe("ajax:before", function(event) { - var message = event.element().readAttribute('data-confirm'); - if (message && !confirm(message)) event.stop(); + var element = event.findElement("a[data-method]"); + if (element && element.readAttribute('data-remote') != 'true') { + var method = element.readAttribute('data-method'), + piggyback = method.toLowerCase() != 'post', + formHTML = formTemplate.interpolate({ + method: 'POST', + realmethod: piggyback ? realmethodTemplate.interpolate({ method: method }) : '', + action: element.readAttribute('href'), + token: authToken, + param: authParam + }); + + var form = new Element('div').update(formHTML).down().hide(); + this.insert({ bottom: form }); + + form.submit(); + event.stop(); + } }); // TODO: I don't think submit bubbles in IE $(document.body).observe("submit", function(event) { + var message = event.element().readAttribute('data-confirm'); + if (message && !confirm(message)) { + event.stop(); + return false; + } + var inputs = event.element().select("input[type=submit][data-disable-with]"); inputs.each(function(input) { input.disabled = true; diff --git a/test/factories/user.rb b/test/factories/user.rb new file mode 100644 index 000000000..9a0d5797d --- /dev/null +++ b/test/factories/user.rb @@ -0,0 +1,29 @@ +Factory.define(:user) do |f| + f.name {Faker::Name.first_name} + f.password_hash {User.sha1("password")} + f.email {Faker::Internet.email} +end + +Factory.define(:banned_user) do |f| + f.is_banned true +end + +Factory.define(:privileged_user) do |f| + f.is_privileged true +end + +Factory.define(:contributor_user) do |f| + f.is_contributor true +end + +Factory.define(:janitor_user) do |f| + f.is_janitor true +end + +Factory.define(:moderator_user) do |f| + f.is_moderator true +end + +Factory.define(:admin_user) do |f| + f.is_admin true +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 45b551fc7..59ef83a93 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,13 +1,12 @@ ENV["RAILS_ENV"] = "test" require File.expand_path(File.dirname(__FILE__) + "/../config/environment") +require 'shoulda' +require 'factory_girl' +require 'mocha' +require 'faker' require 'rails/test_help' -class ActiveSupport::TestCase - # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. - # - # Note: You'll currently still have to declare fixtures explicitly in integration tests - # -- they do not yet inherit this setting - fixtures :all +Dir[File.expand_path(File.dirname(__FILE__) + "/factories/*.rb")].each {|file| require file} - # Add more helper methods to be used by all tests here... +class ActiveSupport::TestCase end diff --git a/test/unit/deleted_post_test.rb b/test/unit/deleted_post_test.rb new file mode 100644 index 000000000..5afbebf21 --- /dev/null +++ b/test/unit/deleted_post_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class DeletedPostTest < ActiveSupport::TestCase + # Replace this with your real tests. + test "the truth" do + assert true + end +end diff --git a/test/unit/pending_post_test.rb b/test/unit/pending_post_test.rb new file mode 100644 index 000000000..b58088cfc --- /dev/null +++ b/test/unit/pending_post_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class PendingPostTest < ActiveSupport::TestCase + # Replace this with your real tests. + test "the truth" do + assert true + end +end diff --git a/test/unit/post_test.rb b/test/unit/post_test.rb new file mode 100644 index 000000000..8afe8cc42 --- /dev/null +++ b/test/unit/post_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class PostTest < ActiveSupport::TestCase + # Replace this with your real tests. + test "the truth" do + assert true + end +end diff --git a/test/unit/post_version_test.rb b/test/unit/post_version_test.rb new file mode 100644 index 000000000..756d5fd54 --- /dev/null +++ b/test/unit/post_version_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class PostVersionTest < ActiveSupport::TestCase + # Replace this with your real tests. + test "the truth" do + assert true + end +end diff --git a/test/unit/tag_test.rb b/test/unit/tag_test.rb new file mode 100644 index 000000000..04498b2a8 --- /dev/null +++ b/test/unit/tag_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class TagTest < ActiveSupport::TestCase + # Replace this with your real tests. + test "the truth" do + assert true + end +end diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb new file mode 100644 index 000000000..5e8358875 --- /dev/null +++ b/test/unit/user_test.rb @@ -0,0 +1,17 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class UserTest < ActiveSupport::TestCase + context "A user" do + setup do + MEMCACHE.flush_all + end + + should "be authenticate" do + @user = Factory.create(:user) + assert(User.authenticate(@user.name, "password"), "Authentication should have succeeded") + assert(!User.authenticate(@user.name, "password2"), "Authentication should not have succeeded") + assert(User.authenticate_hash(@user.name, @user.password_hash), "Authentication should have succeeded") + assert(!User.authenticate_hash(@user.name, "xxxx"), "Authentication should not have succeeded") + end + end +end diff --git a/vendor/cache/abstract-1.0.0.gem b/vendor/cache/abstract-1.0.0.gem new file mode 100644 index 000000000..a9be232aa Binary files /dev/null and b/vendor/cache/abstract-1.0.0.gem differ diff --git a/vendor/cache/arel-0.2.0.gem b/vendor/cache/arel-0.2.0.gem new file mode 100644 index 000000000..a9edd3c68 Binary files /dev/null and b/vendor/cache/arel-0.2.0.gem differ diff --git a/vendor/cache/aws-s3-0.6.2.gem b/vendor/cache/aws-s3-0.6.2.gem new file mode 100644 index 000000000..00fc3083b Binary files /dev/null and b/vendor/cache/aws-s3-0.6.2.gem differ diff --git a/vendor/cache/builder-2.1.2.gem b/vendor/cache/builder-2.1.2.gem new file mode 100644 index 000000000..c90169723 Binary files /dev/null and b/vendor/cache/builder-2.1.2.gem differ diff --git a/vendor/cache/bundler-0.9.2.gem b/vendor/cache/bundler-0.9.2.gem new file mode 100644 index 000000000..d31c2e816 Binary files /dev/null and b/vendor/cache/bundler-0.9.2.gem differ diff --git a/vendor/cache/chardet-0.9.0.gem b/vendor/cache/chardet-0.9.0.gem new file mode 100644 index 000000000..2c7ad52ef Binary files /dev/null and b/vendor/cache/chardet-0.9.0.gem differ diff --git a/vendor/cache/diff-lcs-1.1.2.gem b/vendor/cache/diff-lcs-1.1.2.gem new file mode 100644 index 000000000..aa0be73b2 Binary files /dev/null and b/vendor/cache/diff-lcs-1.1.2.gem differ diff --git a/vendor/cache/erubis-2.6.5.gem b/vendor/cache/erubis-2.6.5.gem new file mode 100644 index 000000000..95bdb17cc Binary files /dev/null and b/vendor/cache/erubis-2.6.5.gem differ diff --git a/vendor/cache/gemcutter-0.3.0.gem b/vendor/cache/gemcutter-0.3.0.gem new file mode 100644 index 000000000..dccbb248b Binary files /dev/null and b/vendor/cache/gemcutter-0.3.0.gem differ diff --git a/vendor/cache/hoe-2.5.0.gem b/vendor/cache/hoe-2.5.0.gem new file mode 100644 index 000000000..96c245d17 Binary files /dev/null and b/vendor/cache/hoe-2.5.0.gem differ diff --git a/vendor/cache/html5-0.10.0.gem b/vendor/cache/html5-0.10.0.gem new file mode 100644 index 000000000..81ee7d0b6 Binary files /dev/null and b/vendor/cache/html5-0.10.0.gem differ diff --git a/vendor/cache/i18n-0.3.3.gem b/vendor/cache/i18n-0.3.3.gem new file mode 100644 index 000000000..4746979a6 Binary files /dev/null and b/vendor/cache/i18n-0.3.3.gem differ diff --git a/vendor/cache/json-1.2.0.gem b/vendor/cache/json-1.2.0.gem new file mode 100644 index 000000000..0fefc67cb Binary files /dev/null and b/vendor/cache/json-1.2.0.gem differ diff --git a/vendor/cache/json_pure-1.2.0.gem b/vendor/cache/json_pure-1.2.0.gem new file mode 100644 index 000000000..f9e2535e9 Binary files /dev/null and b/vendor/cache/json_pure-1.2.0.gem differ diff --git a/vendor/cache/mail-2.1.2.gem b/vendor/cache/mail-2.1.2.gem new file mode 100644 index 000000000..4cf07fa6f Binary files /dev/null and b/vendor/cache/mail-2.1.2.gem differ diff --git a/vendor/cache/mechanize-0.9.3.gem b/vendor/cache/mechanize-0.9.3.gem new file mode 100644 index 000000000..a15f2dc9b Binary files /dev/null and b/vendor/cache/mechanize-0.9.3.gem differ diff --git a/vendor/cache/memcache-client-1.7.8.gem b/vendor/cache/memcache-client-1.7.8.gem new file mode 100644 index 000000000..3f745071f Binary files /dev/null and b/vendor/cache/memcache-client-1.7.8.gem differ diff --git a/vendor/cache/mime-types-1.16.gem b/vendor/cache/mime-types-1.16.gem new file mode 100644 index 000000000..49f1ef203 Binary files /dev/null and b/vendor/cache/mime-types-1.16.gem differ diff --git a/vendor/cache/nokogiri-1.4.1.gem b/vendor/cache/nokogiri-1.4.1.gem new file mode 100644 index 000000000..6a3a88800 Binary files /dev/null and b/vendor/cache/nokogiri-1.4.1.gem differ diff --git a/vendor/cache/pg-0.8.0.gem b/vendor/cache/pg-0.8.0.gem new file mode 100644 index 000000000..5fa38d755 Binary files /dev/null and b/vendor/cache/pg-0.8.0.gem differ diff --git a/vendor/cache/rack-1.1.0.gem b/vendor/cache/rack-1.1.0.gem new file mode 100644 index 000000000..4a73511e8 Binary files /dev/null and b/vendor/cache/rack-1.1.0.gem differ diff --git a/vendor/cache/rack-mount-0.4.5.gem b/vendor/cache/rack-mount-0.4.5.gem new file mode 100644 index 000000000..dcbca2973 Binary files /dev/null and b/vendor/cache/rack-mount-0.4.5.gem differ diff --git a/vendor/cache/rack-test-0.5.3.gem b/vendor/cache/rack-test-0.5.3.gem new file mode 100644 index 000000000..98180f9f9 Binary files /dev/null and b/vendor/cache/rack-test-0.5.3.gem differ diff --git a/vendor/cache/rake-0.8.7.gem b/vendor/cache/rake-0.8.7.gem new file mode 100644 index 000000000..0740cec7b Binary files /dev/null and b/vendor/cache/rake-0.8.7.gem differ diff --git a/vendor/cache/rubyforge-2.0.3.gem b/vendor/cache/rubyforge-2.0.3.gem new file mode 100644 index 000000000..0a516c5ad Binary files /dev/null and b/vendor/cache/rubyforge-2.0.3.gem differ diff --git a/vendor/cache/text-format-1.0.0.gem b/vendor/cache/text-format-1.0.0.gem new file mode 100644 index 000000000..1c46df34f Binary files /dev/null and b/vendor/cache/text-format-1.0.0.gem differ diff --git a/vendor/cache/text-hyphen-1.0.0.gem b/vendor/cache/text-hyphen-1.0.0.gem new file mode 100644 index 000000000..739889b0c Binary files /dev/null and b/vendor/cache/text-hyphen-1.0.0.gem differ diff --git a/vendor/cache/thor-0.13.0.gem b/vendor/cache/thor-0.13.0.gem new file mode 100644 index 000000000..71b5730e0 Binary files /dev/null and b/vendor/cache/thor-0.13.0.gem differ diff --git a/vendor/cache/tzinfo-0.3.16.gem b/vendor/cache/tzinfo-0.3.16.gem new file mode 100644 index 000000000..a0978aad6 Binary files /dev/null and b/vendor/cache/tzinfo-0.3.16.gem differ diff --git a/vendor/cache/xml-simple-1.0.12.gem b/vendor/cache/xml-simple-1.0.12.gem new file mode 100644 index 000000000..675e5c8f6 Binary files /dev/null and b/vendor/cache/xml-simple-1.0.12.gem differ