Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
matthiaseisen committed Oct 14, 2014
0 parents commit f02ee7b
Show file tree
Hide file tree
Showing 59 changed files with 2,938 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
*.py[cod]
21 changes: 21 additions & 0 deletions LICENSE
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2013-2014 Matthias Eisen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
20 changes: 20 additions & 0 deletions README.md
@@ -0,0 +1,20 @@
# Newman

Form mailing for static websites

## Prerequisites

- A Google App Engine account
- A Mailgun Account

## Installation

1. Create a new App Engine app
2. Open src/app.yaml and enter your app id
3. Open src/config.py and edit it according to the comments
4. Deploy your app to App Engine

## Contact

me@matthiaseisen.com

150 changes: 150 additions & 0 deletions src/account.py
@@ -0,0 +1,150 @@
import config
import random
from google.appengine.ext import ndb
from base_handler import BaseHandler
from google.appengine.api import users


class Account(ndb.Model):
api_key_quota = ndb.IntegerProperty(default=config.default_api_key_quota)
number_of_api_keys = ndb.IntegerProperty(default=0)
email = ndb.StringProperty()
created = ndb.DateTimeProperty(auto_now_add=True)
updated = ndb.DateTimeProperty(auto_now=True)

@classmethod
def get_by_api_key(cls, api_key):
result = cls.get_by_id(api_key)
if result:
return result
return cls.query(cls.api_key == api_key).get()


class ApiKey(ndb.Model):
email = ndb.StringProperty()
label = ndb.StringProperty()
user_id = ndb.StringProperty()
created = ndb.DateTimeProperty(auto_now_add=True)
updated = ndb.DateTimeProperty(auto_now=True)

@staticmethod
def random_key(length):
chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
return ''.join(random.choice(chars) for x in xrange(length))

@classmethod
def exists(cls, api_key):
return cls.get_by_id(api_key) and True

@classmethod
def unused_api_key(cls):
new_key = cls.random_key(32)
while cls.exists(new_key):
new_key = cls.random_key(32)
return new_key


class AccountHandler(BaseHandler):
def get(self):
user = self.user()
if user:
account = Account.get_by_id(user.user_id(), parent=self.root.key)
if not account:
account = Account(
id=user.user_id(),
parent=self.root.key,
email=user.email(),
)
api_key = ApiKey(
id=ApiKey.unused_api_key(),
parent=self.root.key,
user_id=user.user_id(),
email='mail@example.com',
label='Default'
)
account.number_of_api_keys += 1
ndb.put_multi([api_key, account])
api_keys = [{
'key': api_key.key.id(),
'email': api_key.email,
'label': api_key.label,
}]
else:
api_keys = [
{
'key': k.key.id(),
'email': k.email or '',
'label': k.label or '',
} for k in ApiKey.query(
ApiKey.user_id == user.user_id(),
ancestor=self.root.key
)
]
self.context.update({
'api_key_quota': account.api_key_quota,
'number_of_api_keys': account.number_of_api_keys,
'api_keys': api_keys,
'email': account.email or '',
'active_page': 'account',
})
return self.render('account.html')
return self.redirect(self.uri_for('login', _full=True))


class ApiKeyHandler(BaseHandler):

def post(self):
user = self.user()
if not user:
return self.error(401)
try:
label = self.request.get('label').strip()
except AttributeError:
label = None
try:
recipient = self.request.get('recipient').strip()
except AttributeError:
recipient = None
api_key = self.request.get('api-key').strip()
api_key = (
api_key and ndb.Key(
'PseudoParent', 'root',
'ApiKey', api_key
).get()
) or None
if api_key:
api_key.email = recipient
api_key.label = label
api_key.put()
else:
account = Account.get_by_id(user.user_id(), parent=self.root.key)
if account and account.number_of_api_keys < account.api_key_quota:
api_key = ApiKey(
id=ApiKey.unused_api_key(),
parent=self.root.key,
user_id=user.user_id(),
email=recipient,
label=label
)
account.number_of_api_keys += 1
ndb.put_multi([api_key, account])
else:
return self.error(400)
return self.redirect(self.uri_for('account'))


class LoginHandler(BaseHandler):
def get(self):
if self.user():
return self.redirect(self.uri_for('account'))
else:
return self.redirect(
users.create_login_url(self.uri_for('account', _full=True))
)


class LogoutHandler(BaseHandler):
def get(self):
return self.redirect(
users.create_logout_url(self.uri_for('index', _full=True))
)
33 changes: 33 additions & 0 deletions src/app.yaml
@@ -0,0 +1,33 @@
# ----------------------
application: your-app-id
# ----------------------
version: 1-0-1
runtime: python27
api_version: 1
threadsafe: true

handlers:

- url: /favicon.ico
static_files: static/img/favicon.ico
upload: static/img/favicon.ico

- url: /robots.txt
static_files: static/robots.txt
upload: static/robots.txt

- url: /static
static_dir: static

- url: /.*
script: main.app

libraries:
- name: webapp2
version: latest
- name: jinja2
version: latest

inbound_services:

- warmup
39 changes: 39 additions & 0 deletions src/base_handler.py
@@ -0,0 +1,39 @@
import os
import webapp2
import jinja2
from google.appengine.api import users
from google.appengine.ext import ndb
import config


class PseudoParent(ndb.Model):
timestamp = ndb.DateTimeProperty(auto_now_add=True)


class BaseHandler(webapp2.RequestHandler):

def __init__(self, request, response):
self.context = {
'static_url': config.static_url,
'meta_title': config.meta_title,
'meta_keywords': config.meta_keywords,
'meta_description': config.meta_description,
'ga_id': config.ga_id,
'contact_api_key': config.contact_api_key,
}
self.root = PseudoParent.get_or_insert('root')
self.jinja = jinja2.Environment(
loader=jinja2.FileSystemLoader(
'/'.join([
os.path.dirname(__file__).rstrip('/'),
'templates'
])
)
)
webapp2.RequestHandler.__init__(self, request, response)

def render(self, tpl):
self.response.write(self.jinja.get_template(tpl).render(self.context))

def user(self):
return users.get_current_user()
31 changes: 31 additions & 0 deletions src/config.py
@@ -0,0 +1,31 @@
# MAILGUN API URL
# (e.g. 'https://api.mailgun.net/v2/mailbot.newmanapi.com/messages')
mailgun_api_url = ''

# MAILGUN API KEY (e.g. 'key-32hfd27d2hd287h2d2d8azd8jwkx7da9')
mailgun_api_key = ''

# MAILGUN SENDER NAME (e.g. 'Newman API')
mailgun_sender_name = ''

# MAILGUN SENDER EMAIL (e.g. 'noreply@mailbot.newmanapi.com')
mailgun_sender_email = ''

# EMAIL SUBJECT (e.g. 'Form Submission')
email_subject = ''

# HTML META INFORMATION
meta_title = 'Newman API'
meta_keywords = 'form, form mailing, contact form, static website'
meta_description = 'Form mailing for static websites'

# GOOGLE ANALYTICS ID (e.g. 'UA-11111111-1')
ga_id = ''

# CONTACT FORM API KEY (e.g. 'c2qrs3vztafh10nntxazfukbzr7ikzmw')
contact_api_key = ''

#MISC
default_api_key_quota = 3 # Newman API keys per user
static_url = '/static/' # location of static files (css, js, etc.)
debug = False # debug mode
17 changes: 17 additions & 0 deletions src/index.yaml
@@ -0,0 +1,17 @@
indexes:

# AUTOGENERATED

# This index.yaml is automatically updated whenever the dev_appserver
# detects that a new type of query is run. If you want to manage the
# index.yaml file manually, remove the above marker line (the line
# saying "# AUTOGENERATED"). If you want to manage some indexes
# manually, move them above the marker line. The index.yaml file is
# automatically uploaded to the admin console when you next deploy
# your application using appcfg.py.

- kind: ApiKey
properties:
- name: user_id
- name: created
direction: desc
11 changes: 11 additions & 0 deletions src/main.py
@@ -0,0 +1,11 @@
import config
import webapp2
import routes

app = webapp2.WSGIApplication(
routes.routes,
debug=config.debug
)

if __name__ == '__main__':
app.run()
15 changes: 15 additions & 0 deletions src/redirect.py
@@ -0,0 +1,15 @@
import webapp2
from base_handler import BaseHandler


class Handler(BaseHandler):

def get(self, page):
self.context.update({
'active_page': page,
'url': (
self.request.get('url') or
webapp2.uri_for('index')
)
})
return self.render('.'.join([page, 'html']))

0 comments on commit f02ee7b

Please sign in to comment.