[
  {
    "path": "README",
    "content": "Sin - The lazy Blogging engine in Sinatra\n\nBased on a conversation with Dan Benjamin, I decided to see how hard it would be to get XMLRPC (MetaWeblog API) working on a minimal site.\nAs of right now (2008-02-17), it has:\n  * posting by anyone through MetaWeblog api (point blog editor at http://site/xml)\n  * uploads with the MetaWeblog api (only small-ish files, bug with Rack or something)\n  * hAtom\n  * A single file for the whole thing"
  },
  {
    "path": "sinatra-blog.rb",
    "content": "# Erik Kastner 2008-02-16 small blog engine with XMLRPC, hAtom and S3 upload (through xlmrpc) support\nrequire 'rubygems'\nrequire 'sinatra'\nrequire 'xmlrpc/marshal'\nrequire 'active_record'\nrequire 'aws/s3'\n\nBUCKET = \"sinatra-data.metaatem.net\"\nActiveRecord::Base.establish_connection(:adapter => \"sqlite3\", :database => \"sin.db\")\n\nbegin\n  ActiveRecord::Schema.define do\n    create_table :posts do |t|\n      t.string :title\n      t.string :author\n      t.text :description\n      t.timestamps\n    end\n  end\nrescue ActiveRecord::StatementInvalid\nend\n\nclass Post < ActiveRecord::Base\n  def permalink; \"/posts/#{to_param}\"; end\n  def full_permalink; \"http://sin.metaatem.net#{permalink}\"; end\n  \n  def to_metaweblog\n    {\n      :dateCreated => created_at,\n      :userid => 1,\n      :postid => id,\n      :description => description,\n      :title => title,\n      :link => \"#{full_permalink}\",\n      :permaLink => \"#{full_permalink}\",\n      :categories => [\"General\"],\n      :date_created_gmt => created_at.getgm,\n    }\n  end\nend\n\n# set utf-8 for outgoing\nbefore_attend do\n  header \"Content-Type\" => \"text/html; charset=utf-8\"\nend\n\nlayout do\n  <<-HTML\n  <html>\n  <head>\n    <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n    <title>Mini Sinatra Blog</title>\n    <link rel=\"alternate\" href=\"feed://subtlety.errtheblog.com/O_o/2d4.xml\" type=\"application/atom+xml\"/>\n    <style type=\"text/css\" media=\"screen\">\n      body { font: 75% \"Helvetica\", arial, Verdana, sans-serif; background: #FFB03B; }\n      h1 { margin: 0; color: #8E2800; }\n      h2 { margin-bottom: 0;}\n      a { color: #468966; }\n      #page { margin: 1em auto; width: 41.667em; border: 0.75em solid #B64926; background: white; padding: 2em; }\n      .post { border-bottom: 0.4em double #FFB03B; padding-bottom: 1em;}\n      .post .subtitle { padding-bottom: 1em; font-size: 83.333%; display: block;}\n      #header, #footer { width: 47em; margin: 0 auto}\n      #header h3 { font-size: 320%; margin: 0.5em 0 0 0; font-family: \"Georgia\";}\n      #header h3 a { color: #FFF0A5; text-decoration: none; text-transform: uppercase; letter-spacing: 0.3em; text-shadow: #B64926 0.04em 0.04em;}\n    </style>\n  </head>\n  <body>\n    <div id=\"header\"><h3><a href=\"/\">Mini Blogin’</a></h3></div>\n    <div id=\"page\">\n      <%= yield %>\n    </div>\n    <div id=\"footer\">&copy; Erik <a href=\"http://metaatem.net/\" alt=\"kastner\">Kastner</a>. Source code on <a href=\"http://pastie.textmate.org/153325\" title=\"#153325 by Erik Kastner (kastner) - Pastie\">pastie</a></div>\n  </body>\n  </html>\n  HTML\nend\n\nget '/' do\n  res = \"<h1>Posts</h1>\"\n  res += <<-HTML\n  <div class=\"post\">\n    <p>Welcome. If you’d like to try adding a post. Point your editor at http://sin.metaatem.net/xml with any username.</p>\n    <p>You can even upload files. However, you can only upload small files until Rack, or Sinatra or SOMEONE fixes TempFile uploads.</p>\n  </div>\n  HTML\n  \n  Post.find(:all, :limit => 20, :order => \"created_at DESC\").each do |p|\n    # hAtom\n    res << <<-HTML\n    <div class=\"post hentry\">\n      <h2><a href=\"#{p.permalink}\" class=\"entry-title\" rel=\"bookmark\">#{p.title}</a></h2>\n      <span class=\"subtitle\">Posted by \n        <span class=\"author vcard fn\">#{p.author}</span> on \n        <abbr class=\"updated\" title=\"#{p.updated_at.getgm.strftime(\"%Y-%m-%dT%H:%M:%SZ\")}\">#{p.updated_at.strftime(\"%D\")}</abbr>\n      </span>\n      <div class=\"content entry-content\">\n        #{p.description}\n      </div>\n    </div>\n    \n    HTML\n  end\n  erb res\nend\n\n# a single post -- sinatra doesn't do head requests in it's dsl yet. this is also DRY\n%w{get head}.each do |meth|\n  Sinatra::Event.new(meth.to_sym, '/posts/:id') do\n    p = Post.find(params[:id])\n    res = \"<h1>#{p.title}</h1>\"\n    res << \"#{p.updated_at.strftime(\"%D\")}\"\n    res << \"<p>#{p.description}</p>\"\n    erb res\n  end\nend\n\n# metaweblog api handler\npost '/xml' do\n  xml = @request.env[\"rack.request.form_vars\"]\n  if xml.empty?\n    hash = @request.env[\"rack.request.query_hash\"]\n    xml = (hash.keys + hash.values).join\n  end\n  \n  raise \"Nothing supplied\" if xml.empty?\n  \n  call = XMLRPC::Marshal.load_call(xml)\n  # convert metaWeblog.getPost to get_post\n  method = call[0].gsub(/metaWeblog\\.(.*)/, '\\1').gsub(/([A-Z])/, '_\\1').downcase\n  \n  header 'Content-Type' => 'text/xml'  \n  send(method, call)\nend\n\ndef get_post(xmlrpc_call)\n  begin\n    post = Post.find(xmlrpc_call[1][0])\n  rescue ActiveRecord::RecordNotFound\n    post = Post.find(xmlrpc_call[1][0].gsub(/^.*posts\\/(\\d+)[^\\d].*$/, '\\1'))\n  end\n  XMLRPC::Marshal.dump_response(post.to_metaweblog)\nend\n\ndef get_recent_posts(xmlrpc_call)\n  posts = Post.find(:all, :limit => 10, :order => \"created_at DESC\")\n  XMLRPC::Marshal.dump_response(posts.map{|p| p.to_metaweblog})\nend\n\ndef new_post(xmlrpc_call)\n  data = xmlrpc_call[1]\n  # blog_id = data[0]; user = data[1]; pass = data[2]\n  post_data = data[3]\n  post = Post.create(:author => data[1], :title => post_data[\"title\"], :description => post_data[\"description\"])\n  XMLRPC::Marshal.dump_response(post.to_metaweblog)\nend\n\ndef edit_post(xmlrpc_call)\n  data = xmlrpc_call[1]\n  post = Post.find(data[0])\n  # user = data[1]; pass = data[2]\n  post_data = data[3]\n  post.update_attributes!(:title => post_data[\"title\"], :description => post_data[\"description\"])\n  XMLRPC::Marshal.dump_response(post.to_metaweblog)\nend\n\ndef get_categories(xmlrpc_call)\n  #res = [{ :categoryId => 1,:parentId => 0,:description => \"General\",:categoryName => \"General\",:htmlUrl => \"http://test.com/categories/1\",:rssUrl => \"http://test.com/categories/1/feed\"}]\n  XMLRPC::Marshal.dump_response(res)\nend\n\ndef new_media_object(xmlrpc_call)\n  post_data = xmlrpc_call[1][3]\n  name = post_data[\"name\"].gsub(/\\//,'')\n  AWS::S3::Base.establish_connection!(\n    :access_key_id => ENV[\"AMAZON_ACCESS_KEY_ID\"],\n    :secret_access_key => ENV[\"AMAZON_SECRET_ACCESS_KEY\"]\n  )\n\n  AWS::S3::S3Object.store(name, post_data[\"bits\"], BUCKET, :access => :public_read)\n  XMLRPC::Marshal.dump_response({\n    :file => name,\n    :url => \"http://s3.amazonaws.com/#{BUCKET}/#{name}\"\n  })\nend"
  }
]