Browse Source

Initial commit

master
Brandon Cornejo 6 years ago
commit
845d5dd3ed
47 changed files with 25143 additions and 0 deletions
  1. 5
    0
      .gitignore
  2. 0
    0
      README.md
  3. 4
    0
      app.py
  4. 66
    0
      app/__init__.py
  5. 8
    0
      app/admin.py
  6. 8
    0
      app/forms.py
  7. 55
    0
      app/models.py
  8. 96
    0
      app/static/css/app.css
  9. BIN
      app/static/img/android.png
  10. BIN
      app/static/img/binary.jpg
  11. BIN
      app/static/img/bitcoins.png
  12. BIN
      app/static/img/facebook.png
  13. BIN
      app/static/img/gnupg.png
  14. BIN
      app/static/img/ios.png
  15. BIN
      app/static/img/openvpn.png
  16. BIN
      app/static/img/osx.png
  17. BIN
      app/static/img/reddit.png
  18. BIN
      app/static/img/twitter.png
  19. BIN
      app/static/img/windows.png
  20. 7643
    0
      app/static/lib/uikit/css/uikit.almost-flat.css
  21. 3
    0
      app/static/lib/uikit/css/uikit.almost-flat.min.css
  22. 6890
    0
      app/static/lib/uikit/css/uikit.css
  23. 7759
    0
      app/static/lib/uikit/css/uikit.gradient.css
  24. 3
    0
      app/static/lib/uikit/css/uikit.gradient.min.css
  25. 3
    0
      app/static/lib/uikit/css/uikit.min.css
  26. BIN
      app/static/lib/uikit/fonts/FontAwesome.otf
  27. BIN
      app/static/lib/uikit/fonts/fontawesome-webfont.eot
  28. BIN
      app/static/lib/uikit/fonts/fontawesome-webfont.ttf
  29. BIN
      app/static/lib/uikit/fonts/fontawesome-webfont.woff
  30. 1780
    0
      app/static/lib/uikit/js/uikit.js
  31. 3
    0
      app/static/lib/uikit/js/uikit.min.js
  32. 14
    0
      app/templates/_macros.html
  33. 84
    0
      app/templates/confirm_purchase.html
  34. 130
    0
      app/templates/dashboard.html
  35. 26
    0
      app/templates/editticket.html
  36. 9
    0
      app/templates/error.html
  37. 88
    0
      app/templates/index.html
  38. 66
    0
      app/templates/layout.html
  39. 26
    0
      app/templates/newticket.html
  40. 24
    0
      app/templates/purchase.html
  41. 23
    0
      app/templates/security/change_password.html
  42. 25
    0
      app/templates/security/forgot_password.html
  43. 31
    0
      app/templates/security/login_user.html
  44. 31
    0
      app/templates/security/register_user.html
  45. 22
    0
      app/templates/security/reset_password.html
  46. 17
    0
      app/templates/viewticket.html
  47. 201
    0
      app/views.py

+ 5
- 0
.gitignore View File

@@ -0,0 +1,5 @@
1
+*.pyc
2
+*.db
3
+*.log
4
+venv/
5
+

+ 0
- 0
README.md View File


+ 4
- 0
app.py View File

@@ -0,0 +1,4 @@
1
+#!venv/bin/python
2
+from app import app
3
+
4
+app.run(host='0.0.0.0', debug=True)

+ 66
- 0
app/__init__.py View File

@@ -0,0 +1,66 @@
1
+from flask import Flask
2
+from flask.ext.sqlalchemy import SQLAlchemy
3
+from flask_mail import Mail
4
+from flask.ext.security import SQLAlchemyUserDatastore, Security, user_registered
5
+from flask.ext.admin import Admin
6
+from flask.ext.admin.contrib.sqla import ModelView
7
+from config import ADMINS, MAILCONF, SECURITY_EMAIL_SENDER
8
+
9
+app = Flask(__name__)
10
+app.config.from_object('config')
11
+
12
+# Setup SQL database and ORM 
13
+db = SQLAlchemy(app)
14
+
15
+# Initialize Flask-Security
16
+from models import User, Role, Ticket, Invoice
17
+user_datastore = SQLAlchemyUserDatastore(db, User, Role)
18
+security = Security(app, user_datastore)
19
+mail = Mail(app)
20
+
21
+# Initialize Flask-Admin
22
+from app import admin
23
+admin = Admin(app, name='PacketCrypt', index_view=admin.AdminIndex())
24
+admin.add_view(ModelView(User, db.session))
25
+admin.add_view(ModelView(Ticket, db.session))
26
+admin.add_view(ModelView(Invoice, db.session))
27
+
28
+@app.before_first_request
29
+def initialize():
30
+    try:
31
+        db.create_all()
32
+        user = user_datastore.find_user(email='br4n@atr0phy.net')
33
+        if not user:
34
+            user = user_datastore.create_user(email='br4n@atr0phy.net', password='packetcrypt')
35
+            user_datastore.add_role_to_user(user, 'Admin')
36
+            app.logger.info("First run, create default admin user")
37
+            for role in ('Admin', 'User'):
38
+                user_datastore.create_role(name=role)
39
+        db.session.commit()
40
+    except Exception, e:
41
+        app.logger.error(str(e))
42
+
43
+@user_registered.connect_via(app)
44
+def on_user_registered(sender, **extra):
45
+    default_role = user_datastore.find_role("User")
46
+    user_datastore.add_role_to_user(user, default_role)
47
+    db.session.commit()
48
+
49
+# Import views
50
+from app import views
51
+
52
+if not app.debug:
53
+    import logging
54
+    from logging.handlers import SMTPHandler, RotatingFileHandler
55
+    credentials = None
56
+    if MAILCONF['MAIL_USERNAME'] or MAILCONF['MAIL_PASSWORD']:
57
+        credentials = (MAILCONF['MAIL_USERNAME'], MAILCONF['MAIL_PASSWORD'])
58
+    mail_handler = SMTPHandler((MAILCONF['MAIL_SERVER'], MAILCONF['MAIL_PORT']), SECURITY_EMAIL_SENDER, ADMINS, 'PacketCrypt failure', credentials)
59
+    mail_handler.setLevel(logging.ERROR)
60
+    app.logger.addHandler(mail_handler)
61
+    file_handler = RotatingFileHandler('app.log', 'a', 1 * 1024 * 1024, 10)
62
+    file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'))
63
+    app.logger.setLevel(logging.INFO)
64
+    file_handler.setLevel(logging.INFO)
65
+    app.logger.addHandler(file_handler)
66
+    app.logger.info('PacketCrypt startup')

+ 8
- 0
app/admin.py View File

@@ -0,0 +1,8 @@
1
+from flask.ext.security import current_user
2
+from flask.ext.admin import AdminIndexView, BaseView, expose
3
+
4
+class AdminIndex(AdminIndexView):
5
+    def is_accessible(self):
6
+        return current_user.has_role('Admin')
7
+
8
+

+ 8
- 0
app/forms.py View File

@@ -0,0 +1,8 @@
1
+from flask.ext.wtf import Form
2
+from wtforms import TextField, SubmitField, TextAreaField
3
+from wtforms.validators import Required
4
+
5
+class TicketForm(Form):
6
+    subject = TextField('Subject', validators = [Required()])
7
+    body = TextAreaField('Message', validators = [Required()])
8
+

+ 55
- 0
app/models.py View File

@@ -0,0 +1,55 @@
1
+from app import db
2
+from flask.ext.sqlalchemy import SQLAlchemy
3
+from flask.ext.security import UserMixin, RoleMixin
4
+
5
+roles_users = db.Table('roles_users',
6
+        db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
7
+        db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
8
+
9
+class Role(db.Model, RoleMixin):
10
+    id = db.Column(db.Integer(), primary_key=True)
11
+    name = db.Column(db.String(80), unique=True)
12
+    description = db.Column(db.String(255))
13
+
14
+class User(db.Model, UserMixin):
15
+    id = db.Column(db.Integer, primary_key=True)
16
+    email = db.Column(db.String(255), unique=True)
17
+    password = db.Column(db.String(255))
18
+    active = db.Column(db.Boolean())
19
+    confirmed_at = db.Column(db.DateTime())
20
+    roles = db.relationship('Role', secondary=roles_users, backref=db.backref('users', lazy='dynamic'))
21
+    tickets = db.relationship('Ticket', backref='creator', lazy='dynamic')
22
+    invoices = db.relationship('Invoice', backref='customer', lazy='dynamic')
23
+
24
+    def __repr__(self):
25
+        return '<User %r>' % (self.email)
26
+
27
+class Ticket(db.Model):
28
+    id = db.Column(db.Integer, primary_key=True)
29
+    subject = db.Column(db.String(140), unique=True)
30
+    body = db.Column(db.String(2000))
31
+    created = db.Column(db.DateTime)
32
+    timestamp = db.Column(db.DateTime)
33
+    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
34
+
35
+    def __repr__(self):
36
+        return '<Ticket %r>' % (self.subject)
37
+
38
+class Invoice(db.Model):
39
+    id = db.Column(db.Integer, primary_key=True)
40
+    is_confirmed = db.Column(db.Boolean())
41
+    paid = db.Column(db.Boolean())
42
+    datepaid = db.Column(db.DateTime)
43
+    datecreated = db.Column(db.DateTime)
44
+    dateends = db.Column(db.DateTime)
45
+    total_btc = db.Column(db.Float)
46
+    exchange_rate_when_paid = db.Column(db.Float)
47
+    address = db.Column(db.String(34))
48
+    confirmations = db.Column(db.Integer)
49
+    transaction_hash = db.Column(db.String)
50
+    input_transaction_hash = db.Column(db.String)
51
+    value_paid = db.Column(db.Float)
52
+    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
53
+
54
+    def __repr__(self):
55
+        return '<Invoice %r>' % (self.id)

+ 96
- 0
app/static/css/app.css View File

@@ -0,0 +1,96 @@
1
+body {
2
+}
3
+
4
+#top-bar {
5
+    position: absolute;
6
+    width:100%;
7
+    top:0;
8
+    left:0;
9
+    background-color: rgba(255,255,255,0.85);
10
+}
11
+
12
+#top-bar div:last-child {
13
+    margin: 1.0%;
14
+}
15
+
16
+#big-top-bar {
17
+    background: url('/static/img/binary.jpg') no-repeat;
18
+    min-height: 400px;
19
+    position:relative;
20
+}
21
+
22
+.pc-market-block {
23
+    max-width: 60%;
24
+    margin-top: 2em;
25
+}
26
+
27
+#tech-images > img {
28
+    border-radius: 1.0;
29
+    padding: 40px 30px;
30
+    display: inline-block;
31
+    width: 100px;
32
+}
33
+
34
+#pc-dashboard-wrapper {
35
+    max-width:60%;
36
+}
37
+
38
+#footer {
39
+    padding-top:4em;
40
+}
41
+
42
+#footer-social > img {
43
+    border-radius: 100px;;
44
+    padding: 0 8px;
45
+    display: inline-block;
46
+    width: 60px;
47
+}
48
+#three-fold-container {
49
+    max-width: 80%;
50
+}
51
+#two-fold-container {
52
+    padding-top:6em;
53
+    max-width:80%;
54
+}
55
+
56
+#container {
57
+}
58
+
59
+#form-container {
60
+    margin-top:20px;
61
+}
62
+
63
+#form-container .uk-form-row > textarea {
64
+    width: 100%;
65
+    height: 200px;
66
+}
67
+
68
+#bitcoin-logo {
69
+    bottom: 10px;
70
+    position: absolute;
71
+    right: 10px;
72
+    width: 200px;
73
+}
74
+
75
+#btc_qr {
76
+    margin: 0 auto;
77
+    display:block
78
+}
79
+
80
+.pc-form-row {
81
+    margin-bottom: 2em;
82
+}
83
+
84
+#form-info {
85
+    width:55%;
86
+}
87
+#form-info > .uk-button {
88
+    margin-top:2em;
89
+}
90
+#form-container > form {
91
+    width:40%;
92
+    display:inline-block;
93
+}
94
+.pc-form-row > input {
95
+    width:100%;
96
+}

BIN
app/static/img/android.png View File


BIN
app/static/img/binary.jpg View File


BIN
app/static/img/bitcoins.png View File


BIN
app/static/img/facebook.png View File


BIN
app/static/img/gnupg.png View File


BIN
app/static/img/ios.png View File


BIN
app/static/img/openvpn.png View File


BIN
app/static/img/osx.png View File


BIN
app/static/img/reddit.png View File


BIN
app/static/img/twitter.png View File


BIN
app/static/img/windows.png View File


+ 7643
- 0
app/static/lib/uikit/css/uikit.almost-flat.css
File diff suppressed because it is too large
View File


+ 3
- 0
app/static/lib/uikit/css/uikit.almost-flat.min.css
File diff suppressed because it is too large
View File


+ 6890
- 0
app/static/lib/uikit/css/uikit.css
File diff suppressed because it is too large
View File


+ 7759
- 0
app/static/lib/uikit/css/uikit.gradient.css
File diff suppressed because it is too large
View File


+ 3
- 0
app/static/lib/uikit/css/uikit.gradient.min.css
File diff suppressed because it is too large
View File


+ 3
- 0
app/static/lib/uikit/css/uikit.min.css
File diff suppressed because it is too large
View File


BIN
app/static/lib/uikit/fonts/FontAwesome.otf View File


BIN
app/static/lib/uikit/fonts/fontawesome-webfont.eot View File


BIN
app/static/lib/uikit/fonts/fontawesome-webfont.ttf View File


BIN
app/static/lib/uikit/fonts/fontawesome-webfont.woff View File


+ 1780
- 0
app/static/lib/uikit/js/uikit.js
File diff suppressed because it is too large
View File


+ 3
- 0
app/static/lib/uikit/js/uikit.min.js
File diff suppressed because it is too large
View File


+ 14
- 0
app/templates/_macros.html View File

@@ -0,0 +1,14 @@
1
+{% macro render_field_with_label(field) %}
2
+{% set css_class=kwargs.pop('class', '') %}
3
+<span class="uk-text-small uk-text-bold">{{ field.label }}:</span>
4
+<div class="uk-form-row pc-form-row">{{ field(class=css_class, **kwargs)|safe }}</div>
5
+{% endmacro %}
6
+
7
+{% macro render_field_with_label_oneline(field) %}
8
+{% set css_class=kwargs.pop('class', '') %}
9
+<div class="uk-form-row">{{field.label}} {{field(class=css_class, **kwargs)|safe }}</div>
10
+{% endmacro %}
11
+
12
+{% macro render_field(field) %}
13
+<div class="uk-form-row">{{ field(class=css_class, **kwargs)|safe }}</div>
14
+{% endmacro %}

+ 84
- 0
app/templates/confirm_purchase.html View File

@@ -0,0 +1,84 @@
1
+{% extends "layout.html" %}
2
+
3
+{% block title %}Confirming purchase - packetcrypt{% endblock %}
4
+
5
+{% block content %}
6
+<div id="form-container" class="uk-width-1-2 uk-panel uk-panel-box uk-container-center">
7
+    <h2>Invoice Created</h2>
8
+    Please send {{ invoice.total_btc - invoice.value_paid}} BTC to: <a href="bitcoin:{{invoice.address}}?amount={{invoice.total_btc}}">{{ invoice.address }}</a>
9
+    <br/><br/>
10
+    <img src="https://chart.googleapis.com/chart?cht=qr&chs=300x300&chl=bitcoin:{{invoice.address}}?amount={{invoice.total_btc}}&callback=?" id="btc_qr" />
11
+    <button class="uk-button uk-button-primary uk-button-expand" id="confirm">Confirm Payment <i class="uk-icon-spinner"></i></button>
12
+    <form action="{{url_for('confirm_purchase', invoice_id=invoice.id)}}" method="POST" name="delete_ticket" style="width:100%;">
13
+    <button type="submit" class="uk-button uk-button-expand" id="cancel">Cancel Payment</button>
14
+    </form>
15
+</div>
16
+
17
+<div id="modal" class="uk-modal">
18
+    <div class="uk-modal-dialog uk-modal-dialog-slide">
19
+        <a class="uk-modal-close uk-close"></a>
20
+        <h1>Thank you!</h1>
21
+        <p>Your payment of <label id="valuepaid"></label> <i class="uk-icon-btc"></i>  has been confirmed.</p>
22
+        <p id="paid">Your VPN service is being configured and should be available from your dashboard in the next 5-10 minutes.</p>
23
+        <p id="notpaid">
24
+            However, this does not cover the total cost for your PacketCrypt service.
25
+            Please send <label id="remaining"></label> <i class="uk-icon-btc"></i> to: <a id="btcaddy" >{{invoice.address}}</a>
26
+        </p>
27
+        <p>Transaction: <a id="hashlink"></a></p>
28
+        <a class="uk-button uk-button-success uk-button-expand" href="{{url_for('dashboard')}}">Return to Dashboard</a>
29
+    </div>
30
+</div>
31
+{% endblock %}
32
+
33
+{% block postscript %}
34
+<script>
35
+    var amountPaidOnLoad = {{invoice.value_paid}};
36
+    $(document).ready(function(){
37
+        var confirmButton = $('#confirm');
38
+        var cancelButton = $('#cancel');
39
+
40
+        confirmButton.click(checkPayment);
41
+
42
+        checkPayment();
43
+
44
+    });
45
+var checkPayment = function (arg){
46
+    $('#confirm > i').addClass('uk-icon-spin'); 
47
+    $.post('/invoice_status', {
48
+            invoice_id: {{invoice.id}}
49
+            }).done(function(d){
50
+                var paidInFull = (d['value_paid'] >= d['total_btc']);
51
+                var transactionHash = d['input_transaction_hash'];
52
+                var confirmations = d['confirmations'];
53
+
54
+                window.setTimeout(stopSpin, 3500);
55
+
56
+                if(confirmations == null || amountPaidOnLoad == d['value_paid']){
57
+                    //$('#flash-wrapper').children(":first").append("<div class='uk-alert uk-alert-warning' data-uk-alert><a class='uk-alert-close uk-close'></a>Unable to confirm payment. Wait a few seconds and try again.</div>");
58
+                    window.setTimeout(checkPayment, 10000);
59
+                } else {
60
+                    // Setup the modal before display
61
+                    var priceLeft = (d['total_btc'] - d['value_paid']);
62
+                    var btcLink = "bitcoin:{{invoice.address}}?amount="+priceLeft;
63
+                    var hashLink = "https://blockchain.info/tx/"+transactionHash;
64
+                    $('#valuepaid').html(d['value_paid']);
65
+                    $('#remaining').html(d['total_btc'] - d['value_paid']);
66
+                    (paidInFull) ? $('#notpaid').css('display', 'none') : $('#paid').css('display', 'none');
67
+                    $('#btcaddy').attr('href', btcLink);
68
+                    $('#hashlink').attr('href', hashLink).html(transactionHash);
69
+
70
+                    // Display teh modal 
71
+                    var modal = new $.UIkit.modal.Modal('#modal');
72
+                    modal.show();
73
+                }
74
+            }).fail(function(){
75
+                $('#flash-wrapper').children(":first").append("<div class='uk-alert uk-alert-danger' data-uk-alert><a href='' class='uk-alert-close uk-close'></a>An error occured while trying to confirm your payment.</div>");
76
+        });
77
+};
78
+
79
+var stopSpin = function (arg){
80
+    $('#confirm > i').removeClass('uk-icon-spin');
81
+};
82
+</script>
83
+{% endblock %}
84
+

+ 130
- 0
app/templates/dashboard.html View File

@@ -0,0 +1,130 @@
1
+{% extends "layout.html" %}
2
+
3
+{% block title %} Dashboard - packetcrypt {% endblock %}
4
+
5
+{% block content %}
6
+<div class="uk-container-center" id="pc-dashboard-wrapper">
7
+    <div class="uk-grid">
8
+        <div class="uk-width-1-1">
9
+            <h4 class="uk-float-right">Hello, {{ user.email }}!</h4>
10
+            <br/>
11
+        </div>
12
+        <div class="uk-width-1-2">
13
+            <div class="uk-panel">
14
+                <h2 class="uk-panel-title">Account Information  <i class="uk-icon-user"></i>  </h2>
15
+                <ul>
16
+                    <li>Email Address: {{ user.email }}</li>
17
+                    <li>Last Payment Date: {{ lastpaid }}</li>
18
+                    <li>Plan Expires: {{ expires }}</li>
19
+                    <li>Number of Support Tickets: {{ user.tickets.all() | length }} </li>
20
+                    <li class="uk-text-muted">Traffic This Month: 000Mb (00%)</li>
21
+                    <li class="uk-text-muted">Number of Referred Signups: 0</li>
22
+                </ul>
23
+            </div>
24
+        </div>
25
+        <div class="uk-width-1-2">
26
+            <div class="uk-panel uk-panel-box uk-panel-box-secondary">
27
+                <h2 class="uk-panel-title">Controls  <i class="uk-icon-cogs uk-float-right"></i>  </h2>
28
+                <hr class="uk-panel-divider">
29
+                <ul class="uk-nav">
30
+                    <li><a href="">Logout</a></li>
31
+                    <li><a href="">Change Password</a></li>
32
+                    <li><a href="{{ url_for('newticket') }}">Open Support Ticket</a></li>
33
+                </ul>
34
+                <div class="uk-panel" style="margin-top:1em;">
35
+                    {% if latest %}
36
+                    <a href="{{url_for('purchase')}}" class="uk-button uk-button-success uk-align-left uk-button-small">Renew</a>
37
+                    {% else %}
38
+                    <a href="{{url_for('purchase')}}" class="uk-button uk-button-success uk-align-left uk-button-small">Purchase</a>
39
+                    {% endif %}
40
+
41
+
42
+                    <div class="uk-button-group uk-align-right" style="">
43
+                        <button class="uk-button uk-button-disabled uk-button-small" type="submit"><i class="uk-icon-folder-open-alt"></i>  Download</button>
44
+                        <div data-uk-dropdown>
45
+                            <a href="" class="uk-button uk-button-small"><i class="uk-icon-caret-down"></i></a>
46
+                            <div class="uk-dropdown uk-dropdown-small">
47
+                                <ul class="uk-nav uk-nav-dropdown">
48
+                                    <li><a href="">Windows</a></li>
49
+                                    <li><a href="">OSX</a></li>
50
+                                    <li><a href="">Android</a></li>
51
+                                    <li><a href="">iOS</a></li>
52
+                                </ul>
53
+                            </div>
54
+                        </div>
55
+                    </div> <!-- end button-group -->
56
+                </div>
57
+            </div>
58
+        </div>
59
+        <div class="uk-width-1-1">
60
+            <table class="uk-table uk-table-hover uk-table-striped">
61
+                <caption>Support Tickets <div class="uk-badge" style="margin-left:1em;">{{ g.user.tickets.all() | length }}</div></caption>
62
+                <thead>
63
+                    <tr><th>Date</th><th>Subject</th><th>Status</th><th>Last Updated</th></tr>
64
+                </thead>
65
+                <tbody>
66
+                    {% if g.user.tickets.all() %}
67
+                    {% for ticket in g.user.tickets.all() %}
68
+                    <tr class="ticket-row">
69
+                        <td>{{ ticket.timestamp |date }}</td>
70
+                        <td><a href="{{ url_for('viewticket', tid=ticket.id) }}"></a>{{ ticket.subject }}</td>
71
+                        <td>N/A</td>
72
+                        <td>N/A</td>
73
+                    </tr>
74
+                    {% endfor %}
75
+                    {% else %}
76
+                    <tr><td>You have no support ticket history</td><td></td><td></td><td></td></tr>
77
+                    {% endif %}
78
+                </tbody>
79
+            </table>
80
+            <br/><br/>
81
+            <table class="uk-table uk-table-hover uk-table-striped">
82
+                <caption>Invoices <div class="uk-badge uk-badge-danger" style="margin-left:1em;">{{ g.user.invoices.all() | length }}</div></caption>
83
+                <thead>
84
+                    <tr><th>Date Paid</th><th>Amount Paid</th><th>Payment Address</th><th>Confirmed</th></tr>
85
+                </thead>
86
+                <tbody>
87
+                    {% if g.user.invoices.all() %}
88
+                    {% for invoice in g.user.invoices.all() %}
89
+                    <tr class="invoice-row">
90
+                        {% if invoice.datepaid %}
91
+                        <td>{{invoice.datepaid | date}}</td>
92
+                        {% else %}
93
+                        <td>Unpaid</td>
94
+                        {% endif %}
95
+                        <td>{{invoice.value_paid}}</td>
96
+                        <td>{{invoice.address}}</td>
97
+                        {% if invoice.is_confirmed %}
98
+                        <td><a href="{{url_for('confirm_purchase', invoice_id=invoice.id)}}"><i class="uk-icon-ok"></i></a></td>
99
+                        {% else %}
100
+                            {% if invoice.paid %}
101
+                            <td><a href="{{url_for('confirm_purchase', invoice_id=invoice.id)}}"><i class="uk-icon-ban-circle"></i></a></td>
102
+                            {% else %}
103
+                            <td><a href="{{url_for('confirm_purchase', invoice_id=invoice.id)}}" class="uk-button">Pay <i class="uk-icon-btc"></i></a></td>
104
+                            {% endif %}
105
+                        {% endif %}
106
+                    </tr>
107
+                    {% endfor %}
108
+                    {% endif %}
109
+                </tbody>
110
+            </table>
111
+        </div>
112
+    </div>
113
+</div>
114
+{% endblock %}
115
+
116
+{% block postscript %}
117
+<script type="text/javascript">
118
+    $(window).load(function(){
119
+            $(".ticket-row").click(function(){
120
+                var tid = $(this).find("a").attr("href");
121
+                window.location = tid
122
+                });
123
+            $(".invoice-row").click(function(){
124
+                var invoice_id = $(this).find("a").attr("href");
125
+                window.location = invoice_id
126
+                });
127
+            });
128
+
129
+</script>
130
+{% endblock %}

+ 26
- 0
app/templates/editticket.html View File

@@ -0,0 +1,26 @@
1
+{% extends "layout.html" %}
2
+{% from "_macros.html" import render_field_with_label, render_field %}
3
+
4
+{% block title %}Update Support Ticket - packetcrypt{% endblock %}
5
+{% block flash %}
6
+{% for field in form.errors %}
7
+{% for error in form.errors[field] %}
8
+<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div>
9
+{% endfor %}
10
+{% endfor %}
11
+{% endblock %}
12
+
13
+{% block content %}
14
+<div id="form-container" class="uk-width-1-2 uk-panel uk-panel-box uk-container-center">
15
+<form action="{{ url_for('editticket', tid=ticket.id) }}" method="POST" name="form" class="uk-form" style="width:100%">
16
+    {{ form.hidden_tag() }}
17
+    {{ render_field_with_label(form.subject) }}
18
+    {{ render_field_with_label(form.body) }}
19
+    <div class="uk-form-row">
20
+        <button class="uk-button uk-button-success" type="submit">Update Ticket</button>
21
+        <a href="{{url_for('dashboard')}}" class="uk-button uk-button-primary">Close</a>
22
+        <a href="{{url_for('deleteticket', tid=ticket.id)}}" class="uk-button uk-button-danger">Delete Ticket</a>
23
+    </div>
24
+</form>
25
+</div>
26
+{% endblock %}

+ 9
- 0
app/templates/error.html View File

@@ -0,0 +1,9 @@
1
+{% extends "layout.html" %}
2
+
3
+{% block content %}
4
+<div id="form-container" class="uk-width-1-4 uk-panel uk-panel-box uk-container-center">
5
+<h1> The file was not found and/or an unexpected error occurred.</h1>
6
+<p>The administrator has been notified. Sorry for the inconvenience!</p>
7
+<p><a href="{{url_for('index')}}" class="uk-button">Back</a></p>
8
+</div>
9
+{% endblock %}

+ 88
- 0
app/templates/index.html View File

@@ -0,0 +1,88 @@
1
+{% extends "layout.html" %}
2
+
3
+{% block title %}PacketCrypt - Secure, anonymous VPN service for humans{% endblock %}
4
+
5
+{% block content %}
6
+<div class="uk-grid">
7
+    <div class="uk-width-1-1">
8
+        <div class="uk-panel uk-container-center pc-market-block uk-text-center">
9
+            <h1>We handle the security, you browse the web.</h1>
10
+            <span class="uk-text-large">
11
+                PacketCrypt offers the the quickest way available to keep your web traffic safe.
12
+                We follow financial industry standards to ensure your data is protected no matter where you're grabbing a connection.
13
+                PacketCrypt makes it simple to encrypt your internet trafic and go about your day.
14
+            </span>
15
+            <div id="tech-images">
16
+                <img src={{ url_for('static', filename='img/windows.png') }} />
17
+                <img src={{ url_for('static', filename='img/openvpn.png') }} />
18
+                <img src={{ url_for('static', filename='img/android.png') }} />
19
+                <img src={{ url_for('static', filename='img/ios.png') }} />
20
+                <img src={{ url_for('static', filename='img/osx.png') }} />
21
+            </div>
22
+        </div>
23
+    </div>
24
+    <div class="uk-width-1-1">
25
+        <div id="three-fold-container" class="uk-container-center">
26
+            <div class="uk-grid" data-uk-grid-match="{target:'.uk-panel'}">
27
+                <div class="uk-width-large-1-3 uk-width-medium-1-1">
28
+                    <div class="uk-panel uk-panel-box uk-panel-box-primary">
29
+                        <h3 class="uk-panel-title uk-text-center"><i class="uk-icon-lock uk-icon-large"></i> Secure</h3>
30
+                        We don't take chances with your data or ours.
31
+                        All of PacketCrypt's services rely on OpenVPN, open source security software trusted by over 5 million users.
32
+                        Run on the safest platform in the world: FreeBSD. Our servers are stable and secured to make sure we're always there for you.
33
+                    </div>
34
+                </div>
35
+                <div class="uk-width-large-1-3 uk-width-medium-1-1">
36
+                    <div class="uk-panel uk-panel-box uk-panel-box-primary">
37
+                        <h3 class="uk-panel-title uk-text-center"><i class="uk-icon-download-alt uk-icon-large"></i> Fast</h3>
38
+                        Unlike other anonymous VPN providers, you don't have to sacrifice speed for safety with PacketCrypt.
39
+                        All of our servers worldwide are on a Gigabit pipeline, so you can download as fast as your local connection can handle.
40
+                        Stream movies or download large files fast, and without leaving a trace.
41
+                    </div>
42
+                </div>
43
+                <div class="uk-width-large-1-3 uk-width-medium-1-1">
44
+                    <div class="uk-panel uk-panel-box uk-panel-box-primary">
45
+                        <h3 class="uk-panel-title uk-text-center"><i class="uk-icon-group uk-icon-large"></i> Anonymous</h3>
46
+                        The first rule of security is <em>Trust No One</em>, and that includes us.
47
+                        PacketCrypt keeps no logs, and doesn't track your login sessions.
48
+                        You pay us in Bitcoin and all we ask for is an email address.
49
+                        Not only does PacketCrypt protect you from the rest of the internet, we protect you from ourselves.
50
+                    </div>
51
+                </div>
52
+            </div>
53
+        </div>
54
+    </div>
55
+    <div class="uk-width-1-1">
56
+        <div id="two-fold-container" class="uk-container-center">
57
+            <div class="uk-grid" data-uk-grid-match="{target:'.uk-panel'}">
58
+                <div class="uk-width-1-3">
59
+                    <div class="uk-panel uk-align-left uk-text-center">
60
+                        <h2>Sign Up Now!</h2>
61
+                        <ul class="uk-list uk-list-striped">
62
+                            <li>No personal information required</li>
63
+                            <li>Only <strong id="btcprice">0</strong><i class="uk-icon-btc"></i> BTC</li>
64
+                            <li>Supports all major platforms</li>
65
+                            <li>Unthrottled Gigabit connection</li>
66
+                            <li>1000Gb of transfer per month!</li>
67
+                        </ul>
68
+                    </div>
69
+                </div>
70
+                <div class="uk-width-2-3">
71
+                    <div class="uk-panel">
72
+                        <h2>Getting started is easy:</h2>
73
+                        <p> 
74
+                            All you need to register is an email address! Bitcoin transactions are processed instantly,
75
+                            and your access will be available within 10 minutes of purchase. Use our custom VPN client
76
+                            for an install-and-go experience or download your client key and use the tools you're
77
+                            familiar with! Click below to sign up for an account and let Packetcrypt secure your web traffic.
78
+                        </p>
79
+                        </span>
80
+                        <a href="{{url_for('purchase')}}" class="uk-button uk-button-primary uk-button-large uk-align-right">Buy Now!</a>
81
+                    </div>
82
+                </div>
83
+            </div>
84
+        </div>
85
+    </div>
86
+</div>
87
+
88
+{% endblock %}

+ 66
- 0
app/templates/layout.html View File

@@ -0,0 +1,66 @@
1
+<html>
2
+    <head>
3
+        <!-- Stylesheets -->
4
+        <link rel="stylesheet" href="{{ url_for('static', filename='lib/uikit/css/uikit.gradient.min.css') }}" />
5
+        <link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}" />
6
+
7
+        <!-- Javascript -->
8
+        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
9
+        <script src="{{ url_for('static', filename='lib/uikit/js/uikit.min.js') }}"></script>
10
+
11
+        <title>{% block title %}{% endblock %}</title>
12
+    </head>
13
+    <body>
14
+        <div id="container" class="uk-grid">
15
+            <div id="big-top-bar" class="uk-width-1-1">
16
+                <div id="top-bar" class="">
17
+                    <div class="uk-float-left"><img src="http://placehold.it/250x65" /></div>
18
+                    <div class="uk-float-right uk-button-group">
19
+                        <a class="uk-button" href="{{ url_for('index') }}">Home</a>
20
+                        <a class="uk-button" href=" {{ url_for('blog') }}">Blog</a>
21
+                        {% if g.user.is_authenticated() %}
22
+                        <a class="uk-button uk-button-success" href="{{ url_for_security('logout') }}">Logoff</a>
23
+                        <a class="uk-button uk-button-primary" href=" {{ url_for('dashboard') }}">Dashboard</a>
24
+                        {% else %}
25
+                        <a class="uk-button uk-button-success" href="{{ url_for_security('login') }}">Login</a>
26
+                        <a class="uk-button uk-button-primary" href="{{ url_for_security('register') }}">Signup</a>
27
+                        {% endif %}
28
+                        {% if g.user.has_role('Admin') %}
29
+                        <a class="uk-button uk-button-danger" href="/admin/">Admin Panel</a>
30
+                        {% endif %}
31
+                    </div> 
32
+                </div>
33
+                <a href="http://bitcoin.org"><img id="bitcoin-logo" src="{{ url_for('static', filename='img/bitcoins.png') }}" /></a>
34
+            </div>
35
+            <div id="flash-wrapper" class="uk-width-1-1">
36
+                <div class="uk-panel uk-width-1-2 uk-container-center">
37
+                    {% with messages = get_flashed_messages() %}
38
+                    {% if messages %}
39
+                        {% for message in messages %}
40
+                        <div class="uk-alert uk-alert-success" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ message }}</div>
41
+                        {% endfor %}
42
+                    {% endif %}
43
+                    {% block flash %}{% endblock %}
44
+                    {% endwith %}
45
+                </div>
46
+            </div>
47
+            <div id="content" class="uk-width-1-1">
48
+                {% block content %}{% endblock %}
49
+            </div>
50
+            <div id="footer" class="uk-width-1-1">
51
+                <hr/>
52
+                <div class="uk-float-left">
53
+                    &copy; 2013 PacketCrypt, Inc. All Rights Reserved. PacketCrypt&trade; is property of PacketCrypt, Inc.<br/> All other marks are the property of their respective owners.
54
+                    <br />
55
+                    <a href="">Terms</a> | <a href="">Privacy Policy</a> | <a href="">Contact us</a>
56
+                </div>
57
+                <div class="uk-float-right" id="footer-social">
58
+                    <img src="{{ url_for('static', filename='img/reddit.png') }}"/>
59
+                    <img src="{{ url_for('static', filename='img/twitter.png') }}"/>
60
+                    <img src="{{ url_for('static', filename='img/facebook.png') }}"/>
61
+                </div>
62
+            </div>
63
+        </div>
64
+    {% block postscript %}{% endblock %}
65
+    </body>
66
+</html>

+ 26
- 0
app/templates/newticket.html View File

@@ -0,0 +1,26 @@
1
+{% extends "layout.html" %}
2
+{% from "_macros.html" import render_field_with_label, render_field %}
3
+
4
+{% block title %}Create Support Ticket - packetcrypt{% endblock %}
5
+{% block flash %}
6
+{% for field in form.errors %}
7
+{% for error in form.errors[field] %}
8
+<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div>
9
+{% endfor %}
10
+{% endfor %}
11
+{% endblock %}
12
+
13
+{% block content %}
14
+<div id="form-container" class="uk-width-1-2 uk-panel uk-panel-box uk-container-center">
15
+<form action="{{ url_for('newticket') }}" method="POST" name="form" class="uk-form" style="width:100%;">
16
+    {{ form.hidden_tag() }}
17
+    {{ render_field_with_label(form.subject) }}
18
+    {{ render_field_with_label(form.body) }}
19
+    <div class="uk-form-row">
20
+        <button class="uk-button uk-button-primary" type="Submit">Create Ticket</button>
21
+        <button class="uk-button uk-button-danger" type="reset">Reset</button>
22
+        <a class="uk-button" href="{{ url_for('dashboard') }}">Cancel</a>
23
+    </div>
24
+</form>
25
+</div>
26
+{% endblock %}

+ 24
- 0
app/templates/purchase.html View File

@@ -0,0 +1,24 @@
1
+{% extends "layout.html" %}
2
+
3
+{% block title %}Purchase VPN Service Now - packetcrypt{% endblock %}
4
+
5
+{% block content %}
6
+<div id="form-container" class="uk-width-1-2 uk-panel uk-panel-box uk-container-center">
7
+    <h4>Purchase PacketCrypt VPN Service</h4>
8
+    <ul>
9
+        <li>Service in 5 different countries</li>
10
+        <li>4 weeks of access</li>
11
+        <li>Price (USD): ${{service_price}}</li>
12
+        {% if price %}
13
+        <li>Price (BTC): {{ price }}</li>
14
+        {% endif %}
15
+    </ul>
16
+    {% if g.user.is_authenticated() %}
17
+    <a href="{{url_for('confirm_purchase')}}" class="uk-button uk-button-success">Purchase</a>
18
+    <a href="{{url_for('dashboard')}}" class="uk-button uk-button-danger">Cancel</a>
19
+    {% else %}
20
+    <a href="{{url_for_security('login')}}" class="uk-button uk-button-primary">Login to Purchase</a>
21
+    <a href="{{url_for_security('register')}}" class="uk-button uk-button-success">Register an Account</a>
22
+    {% endif %}
23
+</div>
24
+{% endblock %}

+ 23
- 0
app/templates/security/change_password.html View File

@@ -0,0 +1,23 @@
1
+{% extends "layout.html" %}
2
+{% from "_macros.html" import render_field_with_label, render_field %}
3
+
4
+{% block title %}Change Password - packetcrypt{% endblock %}
5
+{% block flash %}
6
+{% for field in change_password_form.errors %}
7
+{% for error in change_password_form.errors[field] %}
8
+<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div>
9
+{% endfor %}
10
+{% endfor %}
11
+{% endblock %}
12
+
13
+{% block content %}
14
+<div id="form-container" class="uk-width-1-4 uk-panel uk-panel-box uk-container-center">
15
+<form action="{{ url_for_security('change_password') }}" method="POST" name="change_password_form" class="uk-form">
16
+    {{ change_password_form.hidden_tag() }}
17
+    {{ render_field_with_label(change_password_form.password) }}
18
+    {{ render_field_with_label(change_password_form.new_password) }}
19
+    {{ render_field_with_label(change_password_form.new_password_confirm) }}
20
+    {{ render_field(change_password_form.submit) }}
21
+</form>
22
+</div>
23
+{% endblock %}

+ 25
- 0
app/templates/security/forgot_password.html View File

@@ -0,0 +1,25 @@
1
+{% extends "layout.html" %}
2
+{% from "_macros.html" import render_field_with_label, render_field %}
3
+
4
+{% block title %}Forgot Password - packetcrypt{% endblock %}
5
+{% block flash %}
6
+{% for field in forgot_password_form.errors %}
7
+{% for error in forgot_password_form.errors[field] %}
8
+<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div>
9
+{% endfor %}
10
+{% endfor %}
11
+{% endblock %}
12
+
13
+{% block content %}
14
+<div id="form-container" class="uk-width-3-5 uk-panel uk-panel-box uk-container-center">
15
+<form action="{{ url_for_security('forgot_password') }}" method="POST" name="forgot_password_form" class="uk-form">
16
+    {{ forgot_password_form.hidden_tag() }}
17
+    {{ render_field_with_label(forgot_password_form.email) }}
18
+    {{ render_field(forgot_password_form.submit, class="uk-button") }}
19
+</form>
20
+<div id="form-info" class="uk-align-right uk-text-info">
21
+    Enter the email-address associated with your account to the left.
22
+    Instructions on resetting your password will be emailed to you.
23
+</div>
24
+</div>
25
+{% endblock %}

+ 31
- 0
app/templates/security/login_user.html View File

@@ -0,0 +1,31 @@
1
+{% extends "layout.html" %}
2
+{% from "_macros.html" import render_field_with_label, render_field_with_label_oneline, render_field %}
3
+
4
+{% block title %}Login - packetcrypt{% endblock %}
5
+{% block flash %}
6
+{% for field in login_user_form.errors %}
7
+{% for error in login_user_form.errors[field] %}
8
+<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div>
9
+{% endfor %}
10
+{% endfor %}
11
+{% endblock %}
12
+
13
+{% block content %}
14
+<div id="form-container" class="uk-width-3-5 uk-panel uk-panel-box uk-container-center">
15
+<form action="{{ url_for_security('login') }}" method="POST" name="login_user_form" class="uk-form">
16
+    {{ login_user_form.hidden_tag() }}
17
+    {{ render_field_with_label(login_user_form.email) }}
18
+    {{ render_field_with_label(login_user_form.password) }}
19
+    {{ render_field_with_label_oneline(login_user_form.remember) }}
20
+    {{ render_field(login_user_form.next) }}
21
+    {{ render_field(login_user_form.submit, class="uk-button uk-button-primary") }}
22
+</form>
23
+<div id="form-info" class="uk-align-right uk-text-info">
24
+    Log in with your Packetcrypt email and password to access your dashboard, download the VPN client, and purchase extended service!
25
+    If you are unable to remember your password, please use the button below to reset it.
26
+
27
+    <a href="{{url_for_security('forgot_password')}}" class="uk-button">Forgot Password?</a>
28
+</div>
29
+</div>
30
+{% endblock %}
31
+

+ 31
- 0
app/templates/security/register_user.html View File

@@ -0,0 +1,31 @@
1
+{% extends "layout.html" %}
2
+{% from "_macros.html" import render_field_with_label, render_field %}
3
+
4
+{% block title %}Register - packetcrypt{% endblock %}
5
+{% block flash %}
6
+{% for field in register_user_form.errors %}
7
+{% for error in register_user_form.errors[field] %}
8
+<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div>
9
+{% endfor %}
10
+{% endfor %}
11
+{% endblock %}
12
+
13
+{% block content %}
14
+<div id="form-container" class="uk-width-3-5 uk-panel uk-panel-box uk-container-center">
15
+<form action="{{ url_for_security('register') }}" method="POST" name="register_user_form" class="uk-form" style="display:inline-block;">
16
+    {{ register_user_form.hidden_tag() }}
17
+    {{ render_field_with_label(register_user_form.email) }}
18
+    {{ render_field_with_label(register_user_form.password) }}
19
+    {% if register_user_form.password_confirm %}
20
+    {{ render_field_with_label(register_user_form.password_confirm) }}
21
+    {% endif %}
22
+    {{ render_field(register_user_form.submit, class='uk-button uk-button-primary') }}
23
+</form>
24
+<div id="form-info" class="uk-align-right uk-text-info">
25
+    All we need is an email address and a password to create an account, and you'll be clicks away from browsing securely.
26
+    After registering please check your inbox for a verification email.
27
+
28
+    <br/><p><a href="{{url_for_security('login')}}" class="uk-text-bold uk-text-muted">Click here to sign in to an existing account.</a></p>
29
+</div>
30
+</div>
31
+{% endblock %}

+ 22
- 0
app/templates/security/reset_password.html View File

@@ -0,0 +1,22 @@
1
+{% extends "layout.html" %}
2
+{% from "_macros.html" import render_field_with_label, render_field %}
3
+
4
+{% block title %}Reset Password - packetcrypt{% endblock %}
5
+{% block flash %}
6
+{% for field in reset_password_form.errors %}
7
+{% for error in reset_password_form.errors[field] %}
8
+<div class="uk-alert uk-alert-danger" data-uk-alert><a href="" class="uk-alert-close uk-close"></a>{{ field }}: {{ error }}</div>
9
+{% endfor %}
10
+{% endfor %}
11
+{% endblock %}
12
+
13
+{% block content %}
14
+<div id="form-container" class="uk-width-1-4 uk-panel uk-panel-box uk-container-center">
15
+<form action="{{ url_for_security('reset_password') }}" method="POST" name="reset_password_form" class="uk-form">
16
+    {{ reset_password_form.hidden_tag() }}
17
+    {{ render_field_with_label(reset_password_form.password) }}
18
+    {{ render_field_with_label(reset_password_form.password_confirm) }}
19
+    {{ render_field(reset_password_form.submit) }}
20
+</form>
21
+</div>
22
+{% endblock %}

+ 17
- 0
app/templates/viewticket.html View File

@@ -0,0 +1,17 @@
1
+{% extends "layout.html" %}
2
+
3
+{% block title %}View Support Ticket - packetcrypt{% endblock %}
4
+
5
+{% block content %}
6
+<div id="form-container" class="uk-width-1-2 uk-panel uk-panel-box uk-container-center">
7
+    <h4 class="uk-panel-title">{{ ticket.subject }}</h4>
8
+    <hr class="uk-panel-divider"/>
9
+    <span class="ticket-text">{{ ticket.body }}</span>
10
+    <br/>
11
+    <span class="uk-article-meta">{{ ticket.timestamp }}</span>
12
+    <br/><br/>
13
+    <a href="{{url_for('dashboard')}}" class="uk-button uk-button-primary" type="submit">Close</a>
14
+    <a href="{{url_for('editticket', tid=ticket.id)}}" class="uk-button uk-button-success">Edit</a>
15
+    <a href="{{url_for('deleteticket', tid=ticket.id)}}" class="uk-button uk-button-danger">Delete</a>
16
+</div>
17
+{% endblock %}

+ 201
- 0
app/views.py View File

@@ -0,0 +1,201 @@
1
+from flask import render_template, flash, redirect, g, request, url_for, jsonify
2
+from app import app, db
3
+from models import Ticket, Invoice
4
+from forms import TicketForm 
5
+from flask.ext.security import login_required, current_user
6
+from config import BLOCKCHAIN_URL, SECRET_KEY, STASH_WALLET, PRICE_OF_SERVICE, CONFIRMATION_CAP
7
+import simplejson as json
8
+import urllib2
9
+
10
+# Page routes
11
+@app.route('/')
12
+@app.route('/index')
13
+def index():
14
+    user = g.user
15
+    return render_template("index.html", user=user)
16
+
17
+@app.route('/blog')
18
+def blog():
19
+    return "Da Blog!"
20
+
21
+@app.route('/dashboard')
22
+@login_required
23
+def dashboard():
24
+    user = g.user
25
+    latest_invoice = user.invoices.order_by(Invoice.datepaid.desc()).first()
26
+    lastpaid = latest_invoice.datepaid if latest_invoice else "Unpurchased"
27
+    expires = latest_invoice.dateends if latest_invoice else "No service enabled"
28
+    return render_template("dashboard.html", user=user, latest=latest_invoice, lastpaid=lastpaid, expires=expires)
29
+
30
+@app.route('/newticket', methods=['GET', 'POST'])
31
+@login_required
32
+def newticket():
33
+    user = g.user
34
+    form = TicketForm()
35
+    if form.validate_on_submit():
36
+        import datetime
37
+        t = Ticket()
38
+        form.populate_obj(t)
39
+        t.timestamp = datetime.datetime.utcnow()
40
+        t.created = datetime.datetime.utcnow()
41
+        t.user_id = user.id
42
+        db.session.add(t);
43
+        db.session.commit()
44
+
45
+        flash('New ticket submitted: ' + form.subject.data)
46
+        return redirect('/dashboard')
47
+    return render_template('newticket.html', form=form, user=user)
48
+
49
+@app.route('/viewticket/<int:tid>', methods=['GET', 'POST'])
50
+@login_required
51
+def viewticket(tid):
52
+    user = g.user
53
+    t = Ticket.query.get(tid)
54
+    return render_template('viewticket.html', user=user, ticket=t)
55
+
56
+@app.route('/editticket/<int:tid>', methods=['GET', 'POST'])
57
+@login_required
58
+def editticket(tid):
59
+    user = g.user
60
+    t = Ticket.query.get(tid)
61
+    form = TicketForm(subject=t.subject, body=t.body)
62
+    if form.validate_on_submit():
63
+        import datetime
64
+        form.populate_obj(t)
65
+        t.timestamp = datetime.datetime.utcnow()
66
+        flash("Updated ticket: " + t.subject)
67
+        db.session.commit()
68
+        return redirect('/dashboard')
69
+    return render_template('editticket.html', user=user, ticket=t, form=form)
70
+
71
+@app.route('/deleteticket/<int:tid>', methods=['GET', 'POST'])
72
+@login_required
73
+def deleteticket(tid):
74
+    user = g.user
75
+    t = Ticket.query.get(tid)
76
+    return render_template('viewticket.html', user=user, ticket=t)
77
+
78
+# Start Bitcoin stuff -- blockchain.info api
79
+@app.route('/purchase')
80
+def purchase():
81
+    user = g.user
82
+    try:
83
+        exchange_data = json.load(urllib2.build_opener().open(urllib2.Request("http://blockchain.info/tobtc?currency=USD&value=30")))
84
+    except urllib2.URLError as e:
85
+        flash('Unable to fetch current BTC exchange rate.')
86
+        return render_template('purchase.html', user=user, price=0, service_price=PRICE_OF_SERVICE)
87
+    return render_template('purchase.html', user=user, price=str(exchange_data), service_price=PRICE_OF_SERVICE)
88
+
89
+@app.route('/confirm_purchase/', defaults={'invoice_id': None})
90
+@app.route('/confirm_purchase/<int:invoice_id>', methods=['GET', 'POST'])
91
+@login_required
92
+def confirm_purchase(invoice_id):
93
+    import datetime
94
+    user = g.user
95
+    if invoice_id is None:
96
+        i = Invoice()
97
+        i.paid = False
98
+        i.datecreated = datetime.datetime.utcnow()
99
+        i.user_id = user.id
100
+        db.session.add(i)
101
+        db.session.commit()
102
+        try:
103
+            callback_url = url_for('pay_invoice', _external=True)+'?secret='+SECRET_KEY+'%26invoice_id='+str(i.id)
104
+            url = BLOCKCHAIN_URL+'?method=create&address='+STASH_WALLET+'&callback='+callback_url
105
+            xhr = urllib2.Request(url)
106
+            data = json.load(urllib2.build_opener().open(xhr))
107
+            price_data = json.load(urllib2.build_opener().open(urllib2.Request("http://blockchain.info/tobtc?currency=USD&value="+str(PRICE_OF_SERVICE))))
108
+            exchange_data = json.load(urllib2.build_opener().open(urllib2.Request("http://blockchain.info/ticker")))
109
+            app.logger.info("Sent to blockchain api: " + url)
110
+        except urllib2.URLError as e:
111
+            app.logger.error('Unable to access the blockchain.info api: ' + url)
112
+            flash('There was an error creating a new invoice. Please try again later.')
113
+            return redirect('/dashboard')
114
+        i.address = data['input_address']
115
+        i.total_btc = price_data
116
+        i.exchange_rate_when_paid = exchange_data['USD']['last']
117
+        db.session.commit()
118
+        # TODO: Generate a QR code and/or other e-z payment options for BTC services
119
+        return redirect(url_for('confirm_purchase', invoice_id=i.id))
120
+    else:
121
+        i = Invoice.query.get(invoice_id)
122
+        if request.method == 'POST':
123
+            flash('Invoice ('+i.address+') was deleted succesfully.')
124
+            db.session.delete(i)
125
+            db.session.commit()
126
+            return redirect(url_for('dashboard'))
127
+        return render_template('confirm_purchase.html', user=user, invoice=i, min_confirm=CONFIRMATION_CAP)
128
+
129
+
130
+# AJAX Callbacks
131
+@app.route('/invoice_status', methods=['POST'])
132
+def invoice_status():
133
+    data = request.form
134
+    i = Invoice.query.get(data['invoice_id'])
135
+    if i is None:
136
+        return 0
137
+    return jsonify({
138
+        'confirmations': i.confirmations,
139
+        'value_paid': i.value_paid,
140
+        'total_btc': i.total_btc,
141
+        'input_transaction_hash': i.input_transaction_hash
142
+        })
143
+
144
+@app.route('/pay_invoice', methods=['GET'])
145
+def pay_invoice():
146
+    data = request.args
147
+    if 'test' in data:
148
+        app.logger.info('Test response recieved from Blockchain.info. return: *test*')
149
+        return "*test*"
150
+    if 'secret' in data and data['secret'] == SECRET_KEY:
151
+        import datetime
152
+        i = Invoice.query.get(data['invoice_id'])
153
+        if i is None: 
154
+            # could not find invoice - do we ignore or create?
155
+            app.logger.info("Callback received for non-existant invoice. return: *error*")
156
+            return "*error*"
157
+        if not i.paid:
158
+            i.value_paid = float(data['value']) / 100000000
159
+        else:
160
+            i.value_paid += float(data['value']) / 100000000
161
+        i.datepaid = datetime.datetime.utcnow()
162
+        i.confirmations = data['confirmations']
163
+        i.transaction_hash = data['transaction_hash']
164
+        i.input_transaction_hash = data['input_transaction_hash']
165
+        if i.value_paid == i.total_btc:
166
+            app.logger.info("Invoice {} paid on {} for {} BTC.".format(i.id, i.datepaid, i.value_paid))
167
+            i.paid = True
168
+        db.session.commit()
169
+        if i.paid and i.confirmations > CONFIRMATION_CAP:
170
+            app.logger.info("Invoice {} was confirmed at {}. return: *ok*".format(i.id, i.datepaid))
171
+            i.is_confirmed = True
172
+            i.dateends = i.datepaid + datetime.timedelta(weeks=4)
173
+            return "*ok*"
174
+        app.logger.info("Callback received for invoice {}: awaiting confirmation (current: {}). return: *unconfirmed*".format(i.id, i.confirmations))
175
+        return "*unconfirmed*"
176
+    else:
177
+        app.logger.info('Payment callback with invalid secret key recieved. return: *error*')
178
+        return "*error*"
179
+
180
+# Not routes
181
+@app.errorhandler(404)
182
+def internal_error(error):
183
+    return render_template('error.html'), 404
184
+
185
+@app.errorhandler(500)
186
+def internal_error(error):
187
+    db.session.rollback()
188
+    return render_template('error.html'), 500
189
+
190
+@app.before_request
191
+def before_request():
192
+    g.user = current_user
193
+
194
+@app.template_filter('date')
195
+def _jinja2_filter_datetime(date, fmt=None):
196
+    if not date:
197
+        return None
198
+    if fmt:
199
+        return date.strftime(fmt)
200
+    else:
201
+        return date.strftime("%m/%d/%y (%I:%M%p)")

Loading…
Cancel
Save