├── .coveragerc
├── .env.local.sample
├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ └── flask-pytest.yml
├── .gitignore
├── .vscode
├── launch.json
└── settings.json
├── CHECKS
├── LICENSE.md
├── Makefile
├── Procfile
├── README.md
├── app.json
├── appname
├── __init__.py
├── admin.py
├── api
│ ├── __init__.py
│ ├── info.py
│ ├── resources.py
│ └── user.py
├── assets.py
├── billing_plans.py
├── constants.py
├── controllers
│ ├── __init__.py
│ ├── admin
│ │ ├── __init__.py
│ │ └── jobs.py
│ ├── auth.py
│ ├── dashboard
│ │ ├── __init__.py
│ │ ├── files.py
│ │ ├── home.py
│ │ └── team.py
│ ├── main.py
│ ├── oauth
│ │ ├── __init__.py
│ │ └── google.py
│ ├── realtime.py
│ ├── settings.py
│ ├── store.py
│ └── webhooks
│ │ ├── __init__.py
│ │ └── stripe.py
├── converter.py
├── extensions.py
├── forms
│ ├── __init__.py
│ ├── account.py
│ ├── files.py
│ ├── login.py
│ └── teams.py
├── helpers
│ ├── __init__.py
│ ├── gdpr.py
│ ├── session.py
│ └── view.py
├── mailers
│ ├── __init__.py
│ ├── auth.py
│ ├── notification.py
│ ├── store.py
│ └── teams.py
├── models
│ ├── __init__.py
│ ├── team_file.py
│ ├── teams
│ │ ├── __init__.py
│ │ ├── team.py
│ │ └── team_member.py
│ └── user.py
├── roles.py
├── services
│ ├── __init__.py
│ ├── branding.py
│ ├── hash_ids.py
│ ├── security.py
│ └── stripe.py
├── settings.py
├── static
│ ├── css
│ │ ├── landing.css
│ │ ├── main.css
│ │ ├── overrides.css
│ │ ├── store.css
│ │ └── vendor
│ │ │ └── helper.css
│ ├── js
│ │ └── main.js
│ ├── public
│ │ ├── fonts
│ │ │ ├── .DS_Store
│ │ │ ├── feather
│ │ │ │ ├── feather-webfont.eot
│ │ │ │ ├── feather-webfont.svg
│ │ │ │ ├── feather-webfont.ttf
│ │ │ │ └── feather-webfont.woff
│ │ │ ├── glyphicons-halflings-regular.eot
│ │ │ ├── glyphicons-halflings-regular.svg
│ │ │ ├── glyphicons-halflings-regular.ttf
│ │ │ └── glyphicons-halflings-regular.woff
│ │ ├── ignite
│ │ │ ├── demo-1.png
│ │ │ ├── ignite-icon.png
│ │ │ ├── ignite-icon.svg
│ │ │ ├── ignite-logo.svg
│ │ │ ├── ignite-logo@1x.png
│ │ │ ├── ignite-logo@2x.png
│ │ │ └── stripe-purchase.png
│ │ └── images
│ │ │ ├── .DS_Store
│ │ │ ├── browsers
│ │ │ ├── android-browser.svg
│ │ │ ├── aol-explorer.svg
│ │ │ ├── blackberry.svg
│ │ │ ├── camino.svg
│ │ │ ├── chrome.svg
│ │ │ ├── chromium.svg
│ │ │ ├── dolphin.svg
│ │ │ ├── edge.svg
│ │ │ ├── firefox.svg
│ │ │ ├── ie.svg
│ │ │ ├── maxthon.svg
│ │ │ ├── mozilla.svg
│ │ │ ├── netscape.svg
│ │ │ ├── opera.svg
│ │ │ ├── safari.svg
│ │ │ ├── sleipnir.svg
│ │ │ ├── uc-browser.svg
│ │ │ └── vivaldi.svg
│ │ │ ├── crypto-currencies
│ │ │ ├── bitcoin.svg
│ │ │ ├── cardano.svg
│ │ │ ├── dash.svg
│ │ │ ├── eos.svg
│ │ │ ├── ethereum.svg
│ │ │ ├── litecoin.svg
│ │ │ ├── nem.svg
│ │ │ └── ripple.svg
│ │ │ ├── flags
│ │ │ ├── ad.svg
│ │ │ ├── ae.svg
│ │ │ ├── af.svg
│ │ │ ├── ag.svg
│ │ │ ├── ai.svg
│ │ │ ├── al.svg
│ │ │ ├── am.svg
│ │ │ ├── ao.svg
│ │ │ ├── aq.svg
│ │ │ ├── ar.svg
│ │ │ ├── as.svg
│ │ │ ├── at.svg
│ │ │ ├── au.svg
│ │ │ ├── aw.svg
│ │ │ ├── ax.svg
│ │ │ ├── az.svg
│ │ │ ├── ba.svg
│ │ │ ├── bb.svg
│ │ │ ├── bd.svg
│ │ │ ├── be.svg
│ │ │ ├── bf.svg
│ │ │ ├── bg.svg
│ │ │ ├── bh.svg
│ │ │ ├── bi.svg
│ │ │ ├── bj.svg
│ │ │ ├── bl.svg
│ │ │ ├── bm.svg
│ │ │ ├── bn.svg
│ │ │ ├── bo.svg
│ │ │ ├── bq.svg
│ │ │ ├── br.svg
│ │ │ ├── bs.svg
│ │ │ ├── bt.svg
│ │ │ ├── bv.svg
│ │ │ ├── bw.svg
│ │ │ ├── by.svg
│ │ │ ├── bz.svg
│ │ │ ├── ca.svg
│ │ │ ├── cc.svg
│ │ │ ├── cd.svg
│ │ │ ├── cf.svg
│ │ │ ├── cg.svg
│ │ │ ├── ch.svg
│ │ │ ├── ci.svg
│ │ │ ├── ck.svg
│ │ │ ├── cl.svg
│ │ │ ├── cm.svg
│ │ │ ├── cn.svg
│ │ │ ├── co.svg
│ │ │ ├── cr.svg
│ │ │ ├── cu.svg
│ │ │ ├── cv.svg
│ │ │ ├── cw.svg
│ │ │ ├── cx.svg
│ │ │ ├── cy.svg
│ │ │ ├── cz.svg
│ │ │ ├── de.svg
│ │ │ ├── dj.svg
│ │ │ ├── dk.svg
│ │ │ ├── dm.svg
│ │ │ ├── do.svg
│ │ │ ├── dz.svg
│ │ │ ├── ec.svg
│ │ │ ├── ee.svg
│ │ │ ├── eg.svg
│ │ │ ├── eh.svg
│ │ │ ├── er.svg
│ │ │ ├── es.svg
│ │ │ ├── et.svg
│ │ │ ├── eu.svg
│ │ │ ├── fi.svg
│ │ │ ├── fj.svg
│ │ │ ├── fk.svg
│ │ │ ├── fm.svg
│ │ │ ├── fo.svg
│ │ │ ├── fr.svg
│ │ │ ├── ga.svg
│ │ │ ├── gb-eng.svg
│ │ │ ├── gb-nir.svg
│ │ │ ├── gb-sct.svg
│ │ │ ├── gb-wls.svg
│ │ │ ├── gb.svg
│ │ │ ├── gd.svg
│ │ │ ├── ge.svg
│ │ │ ├── gf.svg
│ │ │ ├── gg.svg
│ │ │ ├── gh.svg
│ │ │ ├── gi.svg
│ │ │ ├── gl.svg
│ │ │ ├── gm.svg
│ │ │ ├── gn.svg
│ │ │ ├── gp.svg
│ │ │ ├── gq.svg
│ │ │ ├── gr.svg
│ │ │ ├── gs.svg
│ │ │ ├── gt.svg
│ │ │ ├── gu.svg
│ │ │ ├── gw.svg
│ │ │ ├── gy.svg
│ │ │ ├── hk.svg
│ │ │ ├── hm.svg
│ │ │ ├── hn.svg
│ │ │ ├── hr.svg
│ │ │ ├── ht.svg
│ │ │ ├── hu.svg
│ │ │ ├── id.svg
│ │ │ ├── ie.svg
│ │ │ ├── il.svg
│ │ │ ├── im.svg
│ │ │ ├── in.svg
│ │ │ ├── io.svg
│ │ │ ├── iq.svg
│ │ │ ├── ir.svg
│ │ │ ├── is.svg
│ │ │ ├── it.svg
│ │ │ ├── je.svg
│ │ │ ├── jm.svg
│ │ │ ├── jo.svg
│ │ │ ├── jp.svg
│ │ │ ├── ke.svg
│ │ │ ├── kg.svg
│ │ │ ├── kh.svg
│ │ │ ├── ki.svg
│ │ │ ├── km.svg
│ │ │ ├── kn.svg
│ │ │ ├── kp.svg
│ │ │ ├── kr.svg
│ │ │ ├── kw.svg
│ │ │ ├── ky.svg
│ │ │ ├── kz.svg
│ │ │ ├── la.svg
│ │ │ ├── lb.svg
│ │ │ ├── lc.svg
│ │ │ ├── li.svg
│ │ │ ├── lk.svg
│ │ │ ├── lr.svg
│ │ │ ├── ls.svg
│ │ │ ├── lt.svg
│ │ │ ├── lu.svg
│ │ │ ├── lv.svg
│ │ │ ├── ly.svg
│ │ │ ├── ma.svg
│ │ │ ├── mc.svg
│ │ │ ├── md.svg
│ │ │ ├── me.svg
│ │ │ ├── mf.svg
│ │ │ ├── mg.svg
│ │ │ ├── mh.svg
│ │ │ ├── mk.svg
│ │ │ ├── ml.svg
│ │ │ ├── mm.svg
│ │ │ ├── mn.svg
│ │ │ ├── mo.svg
│ │ │ ├── mp.svg
│ │ │ ├── mq.svg
│ │ │ ├── mr.svg
│ │ │ ├── ms.svg
│ │ │ ├── mt.svg
│ │ │ ├── mu.svg
│ │ │ ├── mv.svg
│ │ │ ├── mw.svg
│ │ │ ├── mx.svg
│ │ │ ├── my.svg
│ │ │ ├── mz.svg
│ │ │ ├── na.svg
│ │ │ ├── nc.svg
│ │ │ ├── ne.svg
│ │ │ ├── nf.svg
│ │ │ ├── ng.svg
│ │ │ ├── ni.svg
│ │ │ ├── nl.svg
│ │ │ ├── no.svg
│ │ │ ├── np.svg
│ │ │ ├── nr.svg
│ │ │ ├── nu.svg
│ │ │ ├── nz.svg
│ │ │ ├── om.svg
│ │ │ ├── pa.svg
│ │ │ ├── pe.svg
│ │ │ ├── pf.svg
│ │ │ ├── pg.svg
│ │ │ ├── ph.svg
│ │ │ ├── pk.svg
│ │ │ ├── pl.svg
│ │ │ ├── pm.svg
│ │ │ ├── pn.svg
│ │ │ ├── pr.svg
│ │ │ ├── ps.svg
│ │ │ ├── pt.svg
│ │ │ ├── pw.svg
│ │ │ ├── py.svg
│ │ │ ├── qa.svg
│ │ │ ├── re.svg
│ │ │ ├── ro.svg
│ │ │ ├── rs.svg
│ │ │ ├── ru.svg
│ │ │ ├── rw.svg
│ │ │ ├── sa.svg
│ │ │ ├── sb.svg
│ │ │ ├── sc.svg
│ │ │ ├── sd.svg
│ │ │ ├── se.svg
│ │ │ ├── sg.svg
│ │ │ ├── sh.svg
│ │ │ ├── si.svg
│ │ │ ├── sj.svg
│ │ │ ├── sk.svg
│ │ │ ├── sl.svg
│ │ │ ├── sm.svg
│ │ │ ├── sn.svg
│ │ │ ├── so.svg
│ │ │ ├── sr.svg
│ │ │ ├── ss.svg
│ │ │ ├── st.svg
│ │ │ ├── sv.svg
│ │ │ ├── sx.svg
│ │ │ ├── sy.svg
│ │ │ ├── sz.svg
│ │ │ ├── tc.svg
│ │ │ ├── td.svg
│ │ │ ├── tf.svg
│ │ │ ├── tg.svg
│ │ │ ├── th.svg
│ │ │ ├── tj.svg
│ │ │ ├── tk.svg
│ │ │ ├── tl.svg
│ │ │ ├── tm.svg
│ │ │ ├── tn.svg
│ │ │ ├── to.svg
│ │ │ ├── tr.svg
│ │ │ ├── tt.svg
│ │ │ ├── tv.svg
│ │ │ ├── tw.svg
│ │ │ ├── tz.svg
│ │ │ ├── ua.svg
│ │ │ ├── ug.svg
│ │ │ ├── um.svg
│ │ │ ├── un.svg
│ │ │ ├── us.svg
│ │ │ ├── uy.svg
│ │ │ ├── uz.svg
│ │ │ ├── va.svg
│ │ │ ├── vc.svg
│ │ │ ├── ve.svg
│ │ │ ├── vg.svg
│ │ │ ├── vi.svg
│ │ │ ├── vn.svg
│ │ │ ├── vu.svg
│ │ │ ├── wf.svg
│ │ │ ├── ws.svg
│ │ │ ├── ye.svg
│ │ │ ├── yt.svg
│ │ │ ├── za.svg
│ │ │ ├── zm.svg
│ │ │ └── zw.svg
│ │ │ └── payments
│ │ │ ├── 2checkout-dark.svg
│ │ │ ├── 2checkout.svg
│ │ │ ├── alipay-dark.svg
│ │ │ ├── alipay.svg
│ │ │ ├── amazon-dark.svg
│ │ │ ├── amazon.svg
│ │ │ ├── americanexpress-dark.svg
│ │ │ ├── americanexpress.svg
│ │ │ ├── applepay-dark.svg
│ │ │ ├── applepay.svg
│ │ │ ├── bancontact-dark.svg
│ │ │ ├── bancontact.svg
│ │ │ ├── bitcoin-dark.svg
│ │ │ ├── bitcoin.svg
│ │ │ ├── bitpay-dark.svg
│ │ │ ├── bitpay.svg
│ │ │ ├── cirrus-dark.svg
│ │ │ ├── cirrus.svg
│ │ │ ├── clickandbuy-dark.svg
│ │ │ ├── clickandbuy.svg
│ │ │ ├── coinkite-dark.svg
│ │ │ ├── coinkite.svg
│ │ │ ├── dinersclub-dark.svg
│ │ │ ├── dinersclub.svg
│ │ │ ├── directdebit-dark.svg
│ │ │ ├── directdebit.svg
│ │ │ ├── discover-dark.svg
│ │ │ ├── discover.svg
│ │ │ ├── dwolla-dark.svg
│ │ │ ├── dwolla.svg
│ │ │ ├── ebay-dark.svg
│ │ │ ├── ebay.svg
│ │ │ ├── eway-dark.svg
│ │ │ ├── eway.svg
│ │ │ ├── giropay-dark.svg
│ │ │ ├── giropay.svg
│ │ │ ├── googlewallet-dark.svg
│ │ │ ├── googlewallet.svg
│ │ │ ├── ingenico-dark.svg
│ │ │ ├── ingenico.svg
│ │ │ ├── jcb-dark.svg
│ │ │ ├── jcb.svg
│ │ │ ├── klarna-dark.svg
│ │ │ ├── klarna.svg
│ │ │ ├── laser-dark.svg
│ │ │ ├── laser.svg
│ │ │ ├── maestro-dark.svg
│ │ │ ├── maestro.svg
│ │ │ ├── mastercard-dark.svg
│ │ │ ├── mastercard.svg
│ │ │ ├── monero-dark.svg
│ │ │ ├── monero.svg
│ │ │ ├── neteller-dark.svg
│ │ │ ├── neteller.svg
│ │ │ ├── ogone-dark.svg
│ │ │ ├── ogone.svg
│ │ │ ├── okpay-dark.svg
│ │ │ ├── okpay.svg
│ │ │ ├── paybox-dark.svg
│ │ │ ├── paybox.svg
│ │ │ ├── paymill-dark.svg
│ │ │ ├── paymill.svg
│ │ │ ├── payone-dark.svg
│ │ │ ├── payone.svg
│ │ │ ├── payoneer-dark.svg
│ │ │ ├── payoneer.svg
│ │ │ ├── paypal-dark.svg
│ │ │ ├── paypal.svg
│ │ │ ├── paysafecard-dark.svg
│ │ │ ├── paysafecard.svg
│ │ │ ├── payu-dark.svg
│ │ │ ├── payu.svg
│ │ │ ├── payza-dark.svg
│ │ │ ├── payza.svg
│ │ │ ├── ripple-dark.svg
│ │ │ ├── ripple.svg
│ │ │ ├── sage-dark.svg
│ │ │ ├── sage.svg
│ │ │ ├── sepa-dark.svg
│ │ │ ├── sepa.svg
│ │ │ ├── shopify-dark.svg
│ │ │ ├── shopify.svg
│ │ │ ├── skrill-dark.svg
│ │ │ ├── skrill.svg
│ │ │ ├── solo-dark.svg
│ │ │ ├── solo.svg
│ │ │ ├── square-dark.svg
│ │ │ ├── square.svg
│ │ │ ├── stripe-dark.svg
│ │ │ ├── stripe.svg
│ │ │ ├── switch-dark.svg
│ │ │ ├── switch.svg
│ │ │ ├── ukash-dark.svg
│ │ │ ├── ukash.svg
│ │ │ ├── unionpay-dark.svg
│ │ │ ├── unionpay.svg
│ │ │ ├── verifone-dark.svg
│ │ │ ├── verifone.svg
│ │ │ ├── verisign-dark.svg
│ │ │ ├── verisign.svg
│ │ │ ├── visa-dark.svg
│ │ │ ├── visa.svg
│ │ │ ├── webmoney-dark.svg
│ │ │ ├── webmoney.svg
│ │ │ ├── westernunion-dark.svg
│ │ │ ├── westernunion.svg
│ │ │ ├── worldpay-dark.svg
│ │ │ └── worldpay.svg
│ └── tabler
│ │ ├── fonts
│ │ └── feather
│ │ │ ├── feather-webfont.eot
│ │ │ ├── feather-webfont.svg
│ │ │ ├── feather-webfont.ttf
│ │ │ └── feather-webfont.woff
│ │ └── js
│ │ ├── core.js
│ │ ├── plugins
│ │ ├── charts-c3
│ │ │ ├── js
│ │ │ │ ├── c3.min.js
│ │ │ │ └── d3.v3.min.js
│ │ │ ├── plugin.css
│ │ │ └── plugin.js
│ │ ├── fullcalendar
│ │ │ ├── js
│ │ │ │ ├── fullcalendar.min.js
│ │ │ │ └── moment.min.js
│ │ │ ├── plugin.css
│ │ │ └── plugin.js
│ │ ├── iconfonts
│ │ │ ├── fonts
│ │ │ │ ├── materialdesignicons
│ │ │ │ │ ├── materialdesignicons-webfont.eot
│ │ │ │ │ ├── materialdesignicons-webfont.svg
│ │ │ │ │ ├── materialdesignicons-webfont.ttf
│ │ │ │ │ ├── materialdesignicons-webfont.woff
│ │ │ │ │ └── materialdesignicons-webfont.woff2
│ │ │ │ ├── simple-line-icons
│ │ │ │ │ ├── Simple-Line-Icons.eot
│ │ │ │ │ ├── Simple-Line-Icons.svg
│ │ │ │ │ ├── Simple-Line-Icons.ttf
│ │ │ │ │ ├── Simple-Line-Icons.woff
│ │ │ │ │ └── Simple-Line-Icons.woff2
│ │ │ │ ├── themify
│ │ │ │ │ ├── themify.eot
│ │ │ │ │ ├── themify.svg
│ │ │ │ │ ├── themify.ttf
│ │ │ │ │ └── themify.woff
│ │ │ │ └── weathericons
│ │ │ │ │ ├── weathericons-regular-webfont.eot
│ │ │ │ │ ├── weathericons-regular-webfont.svg
│ │ │ │ │ ├── weathericons-regular-webfont.ttf
│ │ │ │ │ ├── weathericons-regular-webfont.woff
│ │ │ │ │ └── weathericons-regular-webfont.woff2
│ │ │ └── plugin.css
│ │ ├── input-mask
│ │ │ ├── js
│ │ │ │ └── jquery.mask.min.js
│ │ │ └── plugin.js
│ │ ├── maps-google
│ │ │ ├── plugin.css
│ │ │ └── plugin.js
│ │ └── prismjs
│ │ │ ├── js
│ │ │ └── prism.pack.js
│ │ │ ├── plugin.css
│ │ │ └── plugin.js
│ │ └── vendors
│ │ ├── bootstrap.bundle.min.js
│ │ ├── chart.bundle.min.js
│ │ ├── circle-progress.min.js
│ │ ├── jquery-3.2.1.min.js
│ │ ├── jquery-3.2.1.slim.min.js
│ │ ├── jquery-jvectormap-2.0.3.min.js
│ │ ├── jquery-jvectormap-de-merc.js
│ │ ├── jquery-jvectormap-world-mill.js
│ │ ├── jquery.sparkline.min.js
│ │ ├── jquery.tablesorter.min.js
│ │ └── selectize.min.js
├── templates
│ ├── admin
│ │ └── index.html
│ ├── auth
│ │ ├── invite.html
│ │ ├── login.html
│ │ ├── oauth_only_login.html
│ │ ├── reauth.html
│ │ ├── request_password_reset.html
│ │ ├── resend_confirmation.html
│ │ ├── reset_password.html
│ │ └── signup.html
│ ├── dashboard
│ │ ├── files.html
│ │ ├── home.html
│ │ └── team.html
│ ├── email
│ │ ├── base.html
│ │ ├── confirm_email.html
│ │ ├── notification.html
│ │ ├── purchase_receipt.html
│ │ ├── reset_password.html
│ │ └── teams
│ │ │ └── invite.html
│ ├── errors
│ │ ├── 404.html
│ │ ├── 500.html
│ │ ├── generic.html
│ │ └── maintenance.html
│ ├── helpers
│ │ ├── _flashes.html
│ │ └── _formhelpers.html
│ ├── lander
│ │ ├── base.html
│ │ ├── index.html
│ │ └── terms.html
│ ├── settings
│ │ ├── account.html
│ │ ├── api.html
│ │ ├── billing.html.jinja2
│ │ ├── change_password.html
│ │ ├── legal_compliance.html
│ │ ├── memberships.html
│ │ ├── oauth.html
│ │ └── sidebar.html
│ ├── store
│ │ ├── base.html
│ │ └── product.html
│ └── tabler
│ │ ├── 401.html
│ │ ├── _footer.html
│ │ ├── _header.html
│ │ ├── _navbar.html
│ │ ├── dashboard_base.html
│ │ └── minimal_base.html
└── utils
│ ├── __init__.py
│ ├── math.py
│ ├── text.py
│ ├── time.py
│ └── token.py
├── docker
├── .dockerignore
└── Dockerfile
├── documentation
├── TODO.md
├── design
│ └── tabler.sketch
├── dokku.md
├── gdpr
│ └── README.md
├── oauth.md
├── screenshots
│ ├── admin-home.png
│ ├── admin.png
│ ├── api-json.png
│ ├── api.png
│ ├── billing-sucess.png
│ ├── billing.png
│ ├── dashboard.png
│ ├── email.png
│ ├── file-uploads.png
│ ├── gdpr.png
│ ├── jobs.png
│ ├── login.png
│ ├── oauth.png
│ ├── reset-pw-submit.png
│ ├── reset.png
│ ├── signup.png
│ ├── stripe-charge.png
│ ├── stripe-console.png
│ └── team.png
└── stripe.md
├── manage.py
├── migrations
├── README
├── alembic.ini
├── env.py
└── script.py.mako
├── requirements.txt
├── runtime.txt
├── setup.cfg
├── tests
├── __init__.py
├── conftest.py
├── test_config.py
├── test_login.py
├── test_team.py
├── test_urls.py
└── test_user.py
├── tmp
└── .touch
└── wsgi.py
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | branch = True
3 | include = appname/*
4 | omit =
5 | *static*
6 | *templates*
7 | *settings*
8 | *manage.py
9 |
10 | [report]
11 | # Regexes for lines to exclude from consideration
12 | exclude_lines =
13 | # Have to re-enable the standard pragma
14 | pragma: no cover
15 |
16 | # Don't complain about missing debug-only code:
17 | def __repr__
18 |
19 | # Don't complain if tests don't hit defensive assertion code:
20 | raise AssertionError
21 | raise NotImplementedError
22 | raise ValueError
23 | pass
24 |
25 | # Don't complain if non-runnable code isn't run:
26 | if 0:
27 | if __name__ == .__main__.:
28 |
29 | ignore_errors = False
30 |
31 | [html]
32 | directory = coverage_report
--------------------------------------------------------------------------------
/.env.local.sample:
--------------------------------------------------------------------------------
1 | export GOOGLE_CONSUMER_KEY='your-key.apps.googleusercontent.com'
2 | export GOOGLE_CONSUMER_SECRET='your-key'
3 | export STRIPE_SECRET_KEY='your-key'
4 | export STRIPE_PUBLISHABLE_KEY='your-key'
5 | export STRIPE_WEBHOOK_KEY='your-key'
6 | export OAUTHLIB_INSECURE_TRANSPORT=1
7 | export STORAGE_KEY='your-s3-key'
8 | export STORAGE_SECRET='your-s3-key'
9 | export STORAGE_PROVIDER='S3'
10 | export FLASK_APP=manage
11 | export FLASK_ENV=development
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | # github: [sumukh]
4 | custom: https://gumroad.com/l/xFvLo
5 |
6 | # patreon: # Replace with a single Patreon username
7 | # open_collective: # Replace with a single Open Collective username
8 | # ko_fi: # Replace with a single Ko-fi username
9 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
10 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
11 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: pip
4 | directory: "/"
5 | schedule:
6 | interval: weekly
7 | time: "09:00"
8 | open-pull-requests-limit: 10
9 | ignore:
10 | - dependency-name: cryptography
11 | versions:
12 | - ">= 3.0.a, < 3.1"
13 | - dependency-name: sqlalchemy
14 | versions:
15 | - 1.3.22
16 | - 1.4.2
17 | - 1.4.3
18 | - 1.4.5
19 | - 1.4.7
20 | - 1.4.9
21 | - dependency-name: humanize
22 | versions:
23 | - 3.3.0
24 | - 3.4.0
25 | - dependency-name: flake8
26 | versions:
27 | - 3.9.0
28 | - dependency-name: flask-migrate
29 | versions:
30 | - 2.6.0
31 | - dependency-name: pytest
32 | versions:
33 | - 6.2.1
34 | - dependency-name: cryptography
35 | versions:
36 | - 3.3.1
37 |
--------------------------------------------------------------------------------
/.github/workflows/flask-pytest.yml:
--------------------------------------------------------------------------------
1 | name: Flask PyTest CI
2 |
3 | on:
4 | push:
5 | branches: [ '*' ]
6 | pull_request:
7 | branches: [ '*' ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 | strategy:
14 | max-parallel: 2
15 | matrix:
16 | python-version: [3.8, 3.9]
17 |
18 | steps:
19 | - uses: actions/checkout@v4
20 | - name: Set up Python ${{ matrix.python-version }}
21 | uses: actions/setup-python@v5
22 | with:
23 | python-version: ${{ matrix.python-version }}
24 | - uses: actions/cache@v4
25 | with:
26 | path: ~/.cache/pip
27 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
28 | restore-keys: |
29 | ${{ runner.os }}-pip-
30 | - name: Install Dependencies
31 | run: |
32 | python -m pip install --upgrade pip
33 | pip install -r requirements.txt
34 |
35 | - name: Run Tests
36 | run: |
37 | py.test --junitxml=test-reports/results.xml --cov-report term-missing --cov=appname tests/
38 |
39 |
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.pyo
3 | *.swp
4 | *~
5 |
6 | # Packages
7 | *.egg
8 | *.egg-info
9 | dist
10 | build/
11 | eggs
12 | bin
13 | sdist
14 |
15 | #Sphinx builds
16 | _build
17 |
18 | # Installer logs
19 | pip-log.txt
20 |
21 | # Flake8 violation file
22 | violations.flake8.txt
23 |
24 | # Unit test / coverage reports
25 | .coverage
26 | .tox
27 | nosetests.xml
28 | __pycache__
29 | database.db
30 | .vscode
31 | .pytest_cache
32 |
33 | # Sphinx
34 | docs/_build
35 |
36 | # Virtual environments
37 | env
38 | env*
39 |
40 | # Env Vars
41 | *.env*
42 |
43 | .webassets-cache
44 | *.cache
45 | .DS_Store
46 | *.sublime-*
47 |
48 | # Temp/Build files
49 | tmp/*
50 | !tmp/.touch
51 | appname/static/public/*
52 | !appname/static/public/images/*
53 | !appname/static/public/ignite/*
54 | !appname/static/public/fonts/*
55 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 |
5 | {
6 | "name": "Flask (For Debugger)",
7 | "type": "python",
8 | "request": "launch",
9 | "stopOnEntry": false,
10 | "python": "${workspaceFolder}/env/bin/python",
11 | "module": "flask.cli",
12 | "cwd": "${workspaceRoot}",
13 | "env": {
14 | "FLASK_APP": "manage",
15 | "FLASK_ENV": "development",
16 | "FLASK_DEBUG": "0",
17 | "LC_ALL": "en_US.utf-8",
18 | "LANG": "en_US.utf-8"
19 | },
20 | "args": [
21 | "run",
22 | "--no-debugger"
23 | ],
24 | "envFile": "${workspaceRoot}/.env",
25 | },
26 | ]
27 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.pythonPath": "${workspaceFolder}/env/bin/python",
3 | "python.testing.pytestArgs": [],
4 | "python.testing.pytestEnabled": true,
5 | "python.linting.pylintEnabled": false,
6 | "python.linting.flake8Enabled": true,
7 | "python.formatting.autopep8Args": [
8 | "--ignore=E302,E123,E133,E226,E241,E242",
9 | "--aggressive"
10 | ],
11 | "python.linting.enabled": true,
12 | // Jinja formatting is not great
13 | "html.format.enable": false,
14 | "python.testing.unittestEnabled": false,
15 | "python.testing.nosetestsEnabled": false
16 | }
--------------------------------------------------------------------------------
/CHECKS:
--------------------------------------------------------------------------------
1 | # Zero Time Deployments
2 | WAIT=5
3 | ATTEMPTS=6
4 | /
5 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: docs test
2 |
3 | help:
4 | @echo " env create a development environment using virtualenv"
5 | @echo " deps install dependencies using pip"
6 | @echo " clean remove unwanted files like .pyc's"
7 | @echo " lint check style with flake8"
8 | @echo " test run all your tests using py.test"
9 |
10 | env:
11 | python3 -m venv env && \
12 | . env/bin/activate && \
13 | make deps
14 |
15 | deps:
16 | pip install -r requirements.txt
17 |
18 | clean:
19 | find . | grep -E "(__pycache__|\.pyc|\.DS_Store|\.db|\.pyo$\)" | xargs rm -rf
20 |
21 | lint:
22 | flake8 --exclude=env .
23 |
24 | test:
25 | py.test tests
26 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: gunicorn -b 0.0.0.0:5000 wsgi:app
2 | work: ./manage.py rq worker
3 | scheduler: ./manage.py rq scheduler
4 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 |
3 | "name": "appname",
4 | "description": "Flask App",
5 | "keywords": [
6 | "flask",
7 | "saas",
8 | "scaffolding",
9 | "ignite"
10 | ],
11 | "website": "https://github.com/sumukh/ignite/",
12 | "repository": "https://github.com/sumukh/ignite/",
13 | "logo": "https://github.com/Sumukh/Ignite/raw/master/appname/static/public/ignite/ignite-icon.png",
14 | "success_url": "/",
15 | "scripts": {
16 | "predeploy": "flask assets build",
17 | "dokku": {
18 | "predeploy": "flask assets build"
19 | }
20 | },
21 | "env": {
22 | "SECRET_TOKEN": {
23 | "description": "A secret key for verifying the integrity of signed cookies.",
24 | "generator": "secret"
25 | },
26 | "FLASK_APP": {
27 | "description": "Where the flask app lives.",
28 | "value": "manage.py"
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/appname/api/info.py:
--------------------------------------------------------------------------------
1 | from flask_restful import marshal_with
2 | from flask_restful import fields
3 |
4 | from appname.api import Resource, BaseAPISchema, API_VERSION
5 |
6 | class APISchema(BaseAPISchema):
7 | get_fields = {
8 | 'version': fields.String,
9 | 'url': fields.String,
10 | 'documentation': fields.String,
11 | }
12 |
13 |
14 | class APIInfo(Resource):
15 | schema = APISchema()
16 |
17 | @marshal_with(schema.get_fields)
18 | def get(self):
19 | return {
20 | 'version': API_VERSION,
21 | 'url': '/api/{0}/info'.format(API_VERSION),
22 | 'documentation': 'Add api_key as a URL query parameter to authenticate'
23 | }
24 |
--------------------------------------------------------------------------------
/appname/api/resources.py:
--------------------------------------------------------------------------------
1 | from flask import redirect, url_for
2 | from flask_restful import abort
3 |
4 | from appname.api import api, api_blueprint, API_VERSION, API_BASE
5 | from appname.api.info import APIInfo
6 | from appname.api.user import CurrentUserInfo
7 |
8 | @api_blueprint.record
9 | def record_params(setup_state):
10 | """ Load used app configs into local config on registration from
11 | appname/__init__.py """
12 | app = setup_state.app
13 | api_blueprint.config['tz'] = app.config.get('TIMEZONE', 'utc') # sample config
14 | api_blueprint.config['debug'] = app.debug
15 |
16 | @api_blueprint.route('/')
17 | def home():
18 | return redirect(url_for('api.apiinfo'))
19 |
20 | api.add_resource(APIInfo, '/{0}/info'.format(API_VERSION))
21 | api.add_resource(CurrentUserInfo, '/{0}/user/current'.format(API_VERSION))
22 |
--------------------------------------------------------------------------------
/appname/api/user.py:
--------------------------------------------------------------------------------
1 | from flask_restful import marshal_with
2 | from flask_login import current_user
3 | from flask_restful import fields, abort
4 |
5 | from appname.api import Resource, BaseAPISchema, API_VERSION
6 |
7 | class CurrentUserInfoSchema(BaseAPISchema):
8 | get_fields = {
9 | 'id': fields.String,
10 | 'email': fields.String,
11 | 'full_name': fields.String,
12 | }
13 |
14 | class CurrentUserInfo(Resource):
15 | schema = CurrentUserInfoSchema()
16 |
17 | @marshal_with(schema.get_fields)
18 | def get(self):
19 | if not current_user.is_authenticated:
20 | abort(401)
21 | return current_user
22 |
--------------------------------------------------------------------------------
/appname/constants.py:
--------------------------------------------------------------------------------
1 | # Constants
2 | import os
3 |
4 | REQUIRE_EMAIL_CONFIRMATION = True
5 | ALLOW_SIGNUPS = True
6 | ALLOW_PASSWORD_LOGIN = True
7 |
8 | # TODO: These shouldn't be in constants (should be in settings)
9 | EMAIL_CONFIRMATION_SALT = os.getenv('EMAIL_CONFIRMATION_KEY', 'email-confirmation-key')
10 | PASSWORD_RESET_SALT = os.getenv('PASSWORD_RESET_KEY', 'pass-reset-key')
11 | PURCHASE_LICENSE_SALT = os.getenv('PASSWORD_RESET_KEY', 'license-purchase-key')
12 |
13 |
14 | PASSWORD_RESET_VALIDITY_SECONDS = 86400
15 |
16 | TEAM_MEMBER_ROLES = ['team member', 'administrator']
17 | MAX_TEAM_SIZE = 50
--------------------------------------------------------------------------------
/appname/controllers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/controllers/__init__.py
--------------------------------------------------------------------------------
/appname/controllers/admin/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/controllers/admin/__init__.py
--------------------------------------------------------------------------------
/appname/controllers/admin/jobs.py:
--------------------------------------------------------------------------------
1 | from flask import abort, current_app
2 | from flask_login import current_user
3 |
4 | import rq_dashboard
5 | jobs = rq_dashboard.blueprint
6 |
7 | @jobs.before_request
8 | def authenticate(*args, **kwargs):
9 | if not current_user.is_authenticated:
10 | return current_app.login_manager.unauthorized()
11 | if not current_user.is_admin:
12 | abort(403)
13 |
--------------------------------------------------------------------------------
/appname/controllers/dashboard/__init__.py:
--------------------------------------------------------------------------------
1 | from .home import blueprint as home_blueprint
2 | from .team import blueprint as team_blueprint
3 | from .files import blueprint as files_blueprint
4 |
5 | dashboard_blueprints = [
6 | home_blueprint,
7 | team_blueprint,
8 | files_blueprint
9 | ]
10 |
--------------------------------------------------------------------------------
/appname/controllers/dashboard/home.py:
--------------------------------------------------------------------------------
1 | from flask import Blueprint, render_template, abort, redirect, url_for, flash
2 | from flask_login import login_required, current_user
3 |
4 | from appname.models.teams import Team
5 |
6 | blueprint = Blueprint('dashboard_home', __name__)
7 |
8 | @blueprint.route('/')
9 | @login_required
10 | def index():
11 | if current_user.active_memberships:
12 | return redirect(url_for('.home', team_id=current_user.active_memberships[0].team.id))
13 | else:
14 | flash("You are not part of any teams", 'warning')
15 | return redirect(url_for('user_settings.memberships'))
16 |
17 | @blueprint.route('/')
18 | @login_required
19 | def home(team_id):
20 | team = Team.query.get(team_id)
21 | if not team or not team.has_member(current_user):
22 | abort(404)
23 | return render_template('dashboard/home.html', team=team)
24 |
--------------------------------------------------------------------------------
/appname/controllers/main.py:
--------------------------------------------------------------------------------
1 | import stripe
2 |
3 | from flask import Blueprint, render_template, redirect, url_for
4 | from flask_login import current_user
5 |
6 | from appname.extensions import cache
7 |
8 | main = Blueprint('main', __name__)
9 |
10 | @main.route('/')
11 | def home():
12 | if current_user.is_authenticated:
13 | return redirect(url_for('dashboard_home.index'))
14 | return render_template('lander/index.html', stripe_publishable_key=stripe.publishable_key)
15 |
16 | @main.route('/terms')
17 | def terms():
18 | return render_template('lander/terms.html')
19 |
20 | @main.route('/privacy')
21 | def privacy():
22 | return render_template('lander/terms.html')
23 |
24 | @main.route('/beta')
25 | @cache.cached(timeout=1000, unless=lambda: current_user.is_authenticated)
26 | def beta():
27 | return "Coming Soon", 200
28 |
--------------------------------------------------------------------------------
/appname/controllers/oauth/__init__.py:
--------------------------------------------------------------------------------
1 | from .google import blueprint as google_blueprint
2 |
--------------------------------------------------------------------------------
/appname/controllers/realtime.py:
--------------------------------------------------------------------------------
1 | from flask_login import current_user
2 |
3 | from flask_socketio import join_room, leave_room, send
4 | from appname.extensions import socketio
5 |
6 | @socketio.on('connect')
7 | def connect():
8 | if not current_user.is_authenticated:
9 | pass
10 | return
11 |
12 | @socketio.on('disconnect')
13 | def disconnect():
14 | if not current_user.is_authenticated:
15 | pass
16 | return
17 |
18 | @socketio.on('join')
19 | def on_join(data):
20 | username = data['username']
21 | room = data['room']
22 | join_room(room)
23 | send(username + ' has entered the room.', room=room)
24 |
25 | @socketio.on('leave')
26 | def on_leave(data):
27 | username = data['username']
28 | room = data['room']
29 | leave_room(room)
30 | send(username + ' has left the room.', room=room)
31 |
--------------------------------------------------------------------------------
/appname/controllers/store.py:
--------------------------------------------------------------------------------
1 | import stripe
2 |
3 | from flask import Blueprint, flash, render_template, request
4 |
5 | from appname.models.user import User
6 | from appname.extensions import csrf
7 |
8 | from appname.mailers.store import PurchaseReceipt
9 |
10 | store = Blueprint('store', __name__)
11 |
12 | @store.route('/store')
13 | def home():
14 | return render_template('store/product.html', stripe_publishable_key=stripe.publishable_key)
15 |
16 |
17 | @store.route('/store/payment', methods=['POST'])
18 | @csrf.exempt
19 | def payment():
20 |
21 | customer = stripe.Customer.create(
22 | email=request.form['stripeEmail'],
23 | source=request.form['stripeToken']
24 | )
25 |
26 | stripe.Charge.create(
27 | customer=customer.id,
28 | amount=4999,
29 | currency='usd',
30 | description='Ingite Flask App Code'
31 | )
32 |
33 | user = User.lookup_or_create_by_email(request.form['stripeEmail'])
34 | PurchaseReceipt(user).send()
35 |
36 | flash("Payment processed. You'll get an email shortly", 'success')
37 |
38 | return render_template('store/product.html')
39 |
--------------------------------------------------------------------------------
/appname/controllers/webhooks/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/controllers/webhooks/__init__.py
--------------------------------------------------------------------------------
/appname/converter.py:
--------------------------------------------------------------------------------
1 | from werkzeug.routing import BaseConverter, ValidationError
2 |
3 | from appname.extensions import hashids
4 |
5 | class BoolConverter(BaseConverter):
6 | def __init__(self, url_map, false_value, true_value):
7 | super(BoolConverter, self).__init__(url_map)
8 | self.false_value = false_value
9 | self.true_value = true_value
10 | self.regex = '(?:{0}|{1})'.format(false_value, true_value)
11 |
12 | def to_python(self, value):
13 | return value == self.true_value
14 |
15 | def to_url(self, value):
16 | return self.true_value if value else self.false_value
17 |
18 | class HashidConverter(BaseConverter):
19 | def to_python(self, value):
20 | try:
21 | return hashids.decode_id(value)
22 | except (TypeError, ValueError) as e:
23 | raise ValidationError(str(e))
24 |
25 | def to_url(self, value):
26 | return hashids.encode_id(value)
27 |
28 | class CustomConverters:
29 | def init_app(self, app):
30 | app.url_map.converters['hashid'] = HashidConverter
31 | app.url_map.converters['bool'] = BoolConverter
32 |
33 |
34 | custom_converters = CustomConverters()
35 |
--------------------------------------------------------------------------------
/appname/forms/__init__.py:
--------------------------------------------------------------------------------
1 | from flask_wtf import FlaskForm
2 | from wtforms import StringField
3 |
4 | def strip_whitespace(value):
5 | if value and hasattr(value, "strip"):
6 | return value.strip()
7 | else:
8 | return value
9 |
10 | class BaseForm(FlaskForm):
11 | class Meta:
12 | def bind_field(self, form, unbound_field, options):
13 | filters = unbound_field.kwargs.get('filters', [])
14 | field_type = type(unbound_field)
15 | if field_type == StringField:
16 | filters.append(strip_whitespace)
17 | return unbound_field.bind(form=form, filters=filters, **options)
18 |
19 |
20 | class SimpleForm(BaseForm):
21 | pass # Used for forms that have no input (other than a CSRF check)
22 |
--------------------------------------------------------------------------------
/appname/forms/account.py:
--------------------------------------------------------------------------------
1 | from appname.forms import BaseForm
2 | from wtforms import validators, StringField
3 |
4 | class ChangeProfileForm(BaseForm):
5 | name = StringField('Name', validators=[validators.InputRequired()])
6 |
--------------------------------------------------------------------------------
/appname/forms/files.py:
--------------------------------------------------------------------------------
1 | from appname.forms import BaseForm
2 | from wtforms import validators, TextAreaField, FileField
3 |
4 | class FileForm(BaseForm):
5 | description = TextAreaField('Description')
6 | attachment = FileField('Attachment', validators=[validators.InputRequired()])
7 |
--------------------------------------------------------------------------------
/appname/forms/teams.py:
--------------------------------------------------------------------------------
1 | from appname.forms import BaseForm
2 | from wtforms import validators, StringField, SelectField
3 |
4 | from appname.constants import TEAM_MEMBER_ROLES
5 |
6 | class InviteMemberForm(BaseForm):
7 | email = StringField('Email', validators=[validators.email(), validators.InputRequired()])
8 | role = SelectField('Role', default='team member',
9 | choices=[(r, r.title()) for r in TEAM_MEMBER_ROLES])
10 |
--------------------------------------------------------------------------------
/appname/helpers/__init__.py:
--------------------------------------------------------------------------------
1 | # Helpers encompass logic that doesn't belong in controllers or
2 | # in views. They are application specific and usually cannot be
3 | # directly reused.
4 |
--------------------------------------------------------------------------------
/appname/helpers/session.py:
--------------------------------------------------------------------------------
1 | # Utility methods to grab info from the current session
2 | from flask import session, redirect, flash, url_for
3 | from flask_login import current_user
4 |
5 | def current_membership():
6 | session_id = session.get('current_team_membership_id')
7 | memberships = current_user.active_memberships
8 | if not session_id:
9 | return memberships[0]
10 | elif session_id and session_id in [m.id for m in memberships]:
11 | return [m for m in memberships if m.id == session_id][0]
12 | else:
13 | # TODO: Should just raise an exception here.
14 | flash('You currently do not have accesss to appname', 'warning')
15 | return redirect(url_for("main.home"))
16 |
--------------------------------------------------------------------------------
/appname/helpers/view.py:
--------------------------------------------------------------------------------
1 | # View Helpers that we want to be available globally in Jinja templates
2 | # as `view_helpers.method`
3 |
4 | def format_money(amount, currency):
5 | return "{0} {1}".format(amount, currency)
6 |
--------------------------------------------------------------------------------
/appname/mailers/auth.py:
--------------------------------------------------------------------------------
1 | from flask import render_template, url_for
2 |
3 | import appname.constants as constants
4 | from appname.mailers import Mailer
5 | from appname.extensions import token
6 |
7 | class ConfirmEmail(Mailer):
8 | TEMPLATE = 'email/confirm_email.html'
9 | DEFAULT_SUBJECT = "Confirm your email on appname"
10 |
11 | def send(self):
12 | if self.recipient.email_confirmed:
13 | return False
14 | user_token = token.generate(
15 | self.recipient.email, salt=constants.EMAIL_CONFIRMATION_SALT)
16 | link = url_for('auth.confirm', code=user_token, _external=True)
17 | html_body = render_template(self.TEMPLATE, link=link)
18 | return self.deliver_later(self.recipient_email, self.subject, html_body)
19 |
20 | class ResetPassword(Mailer):
21 | TEMPLATE = 'email/reset_password.html'
22 | DEFAULT_SUBJECT = "appname Password Reset"
23 |
24 | def send(self):
25 | user_token = token.generate(
26 | self.recipient.email, salt=constants.PASSWORD_RESET_SALT)
27 | link = url_for('auth.reset_password', code=user_token, _external=True)
28 | html_body = render_template(self.TEMPLATE, link=link)
29 | return self.deliver_later(self.recipient_email, self.subject, html_body)
30 |
--------------------------------------------------------------------------------
/appname/mailers/notification.py:
--------------------------------------------------------------------------------
1 | from flask import render_template
2 |
3 | from appname.mailers import Mailer
4 |
5 | class NotificationMailer(Mailer):
6 | TEMPLATE = 'email/notification.html'
7 | DEFAULT_SUBJECT = "[appname] New notification"
8 |
9 | def __init__(self, user, subject, text, link=None, attachments=None):
10 | self.recipient = None
11 | self._subject = subject
12 | self.text = text
13 | self.recipient_email = user
14 | self.link = link
15 | self.attachments = attachments or []
16 |
17 | @property
18 | def subject(self):
19 | return self._subject or self.DEFAULT_SUBJECT
20 |
21 | def send(self):
22 | html_body = render_template(self.TEMPLATE, body=self.text, link=self.link, link_text="View on Appname")
23 | return self.deliver_now(self.recipient_email, self.subject, html_body,
24 | attachments=self.attachments)
25 |
--------------------------------------------------------------------------------
/appname/mailers/store.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime as dt
2 | from flask import render_template
3 |
4 | import appname.constants as constants
5 | from appname.mailers import Mailer
6 | from appname.extensions import token
7 |
8 | class PurchaseReceipt(Mailer):
9 | TEMPLATE = 'email/purchase_receipt.html'
10 | DEFAULT_SUBJECT = "Your purchase of Ignite Starter"
11 |
12 | def send(self):
13 | key = "{email}-{timestamp}".format(email=self.recipient.email, timestamp=dt.now())
14 | license = token.generate(key, salt=constants.PURCHASE_LICENSE_SALT)
15 | html_body = render_template(self.TEMPLATE, license=license)
16 | return self.deliver_now(self.recipient_email, self.subject, html_body)
17 |
--------------------------------------------------------------------------------
/appname/mailers/teams.py:
--------------------------------------------------------------------------------
1 | from flask import render_template, url_for
2 |
3 | from appname.mailers import Mailer
4 | from appname.extensions import branding
5 | class InviteEmail(Mailer):
6 | TEMPLATE = 'email/teams/invite.html'
7 |
8 | def __init__(self, invite):
9 | self.recipient = None
10 | self.invite = invite
11 | self.recipient_email = invite.invite_email or (invite.user and invite.user.email)
12 |
13 | @property
14 | def subject(self):
15 | return ("{0} invited you to join their team on {1}".format(self.invite.inviter.email, branding.name))
16 |
17 | def send(self):
18 | link = url_for('auth.invite_page', invite_id=self.invite.id,
19 | secret=self.invite.invite_secret, _external=True)
20 | html_body = render_template(self.TEMPLATE, link=link, invite=self.invite)
21 | return self.deliver_now(self.recipient_email, self.subject, html_body)
22 |
--------------------------------------------------------------------------------
/appname/models/team_file.py:
--------------------------------------------------------------------------------
1 | from appname.models import db, Model
2 |
3 | class TeamFile(Model):
4 | id = db.Column(db.Integer(), primary_key=True)
5 | team_id = db.Column(db.ForeignKey("team.id"), index=True,
6 | nullable=False)
7 | user_id = db.Column(db.ForeignKey("user.id"), index=True,
8 | nullable=True)
9 |
10 | file_name = db.Column(db.String())
11 | description = db.Column(db.String())
12 |
13 | file_object_name = db.Column(db.String())
14 |
15 | activated = db.Column(db.Boolean(), default=False)
16 |
17 | team = db.relationship("Team", backref='files', lazy="joined")
18 | user = db.relationship("User", backref='team_files')
19 |
20 | GDPR_EXPORT_COLUMNS = {
21 | "created": "When the invite was created",
22 | "team_id": "What team was the invite for",
23 | "creator_id": "Who created the file",
24 | "file_name": "The name of the file",
25 | "description": "The description of the file",
26 | }
27 |
--------------------------------------------------------------------------------
/appname/models/teams/__init__.py:
--------------------------------------------------------------------------------
1 | # To get Flask Migrate to pick these up, import this module in appname/manage.py
2 | from .team_member import TeamMember # noqa
3 | from .team import Team # noqa
4 |
--------------------------------------------------------------------------------
/appname/roles.py:
--------------------------------------------------------------------------------
1 | """ This file defines utilities used to enforce user roles. """
2 | from flask import current_user
3 | from functools import wraps
4 | from enum import Enum
5 |
6 | class Roles(Enum):
7 | GUEST = 'guest'
8 | USER = 'user'
9 | ADMIN = 'admin'
10 |
11 | def requires_roles(*roles):
12 | def wrapper(f):
13 | def get_user_role():
14 | if current_user.is_anonymous:
15 | return Roles.guest
16 | return current_user.role
17 |
18 | @wraps(f)
19 | def wrapped(*args, **kwargs):
20 | if get_user_role() not in roles:
21 | raise
22 | return f(*args, **kwargs)
23 | return wrapped
24 | return wrapper
25 |
--------------------------------------------------------------------------------
/appname/services/__init__.py:
--------------------------------------------------------------------------------
1 | # Things that could be their own stand alone libraries go here.
2 |
3 | # Usually follows the application factory pattern: A class with a
4 | # create_app method
5 |
--------------------------------------------------------------------------------
/appname/services/branding.py:
--------------------------------------------------------------------------------
1 | import os
2 | class Branding:
3 | def __init__(self):
4 | self.environment = "prod"
5 | self.config = {}
6 |
7 | def init_app(self, app):
8 | self.config = app.config
9 | self.environment = app.config.get('ENV', 'prod')
10 |
11 | @property
12 | def name(self):
13 | if self.environment == "dev":
14 | return "appname-dev"
15 | return "appname"
16 |
17 | @property
18 | def support_email(self):
19 | email = self.config.get('support_email', 'help@example.com')
20 | return email
21 |
22 | @property
23 | def icon_path(self):
24 | return "public/ignite/ignite-logo@2x.png"
25 |
26 | @property
27 | def svg_icon(self):
28 | return "public/ignite/ignite-icon.svg"
29 |
30 | @property
31 | def website_domain(self):
32 | return "appname.com"
33 |
34 | @property
35 | def legal_name(self):
36 | return "appname.com"
37 |
38 | @property
39 | def corporate_jurisdiction(self):
40 | return "United States"
41 |
42 | @property
43 | def full_logo_path(self):
44 | return "public/ignite/ignite-logo@2x.png"
45 |
46 |
--------------------------------------------------------------------------------
/appname/services/hash_ids.py:
--------------------------------------------------------------------------------
1 | from hashids import Hashids
2 |
3 | class HashIds:
4 | def init_app(self, app):
5 | salt = app.config.get('SECRET_KEY', 'appname-hashids-secret')
6 | self.hashids = Hashids(min_length=5, salt=salt)
7 |
8 | def encode_id(self, id_number):
9 | return self.hashids.encode(id_number)
10 |
11 | def decode_id(self, value):
12 | numbers = self.hashids.decode(value)
13 | if len(numbers) != 1:
14 | raise ValueError('Could not decode hash {0} into ID'.format(value))
15 | return numbers[0]
16 |
--------------------------------------------------------------------------------
/appname/services/security.py:
--------------------------------------------------------------------------------
1 | import hashlib
2 | from itsdangerous import URLSafeTimedSerializer
3 |
4 | class Token:
5 | def init_app(self, app):
6 | self.app = app
7 | encoded_secret = app.config["SECRET_KEY"].encode()
8 | self.ts = URLSafeTimedSerializer(encoded_secret)
9 | self.unique_salt = hashlib.md5(encoded_secret).hexdigest()[:5]
10 |
11 | def generate(self, key, salt='default-salt'):
12 | return self.ts.dumps(key, salt + self.unique_salt)
13 |
14 | def decode(self, token, salt='default-salt', max_age=86400):
15 | """Decode a token and return the original value or raise an Exception.
16 |
17 | Parameters
18 | ----------
19 | token : str
20 | Token to decode.
21 | salt : str, optional
22 | Salt used when generating the token. ``'default-salt'`` by default.
23 | max_age : int, optional
24 | Maximum age in seconds before the token expires. ``86400`` by
25 | default.
26 | """
27 |
28 | return self.ts.loads(token, salt=salt + self.unique_salt, max_age=max_age)
29 |
--------------------------------------------------------------------------------
/appname/static/css/main.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Lato:300,400,700');
2 | body {
3 | font-family: 'Lato', -apple-system, BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";
4 | }
5 |
6 | .root-container {
7 | margin-top: 20px;
8 | }
9 |
10 | .footer {
11 | bottom: 0px;
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/appname/static/css/overrides.css:
--------------------------------------------------------------------------------
1 | hr.thin {
2 | margin-top: 1rem !important;
3 | margin-bottom: 1rem !important;
4 | }
5 |
--------------------------------------------------------------------------------
/appname/static/css/store.css:
--------------------------------------------------------------------------------
1 | .hero-header {
2 | height: 175px;
3 | background-image: linear-gradient(90deg,#1f3152 0,#3e5e83 100%);
4 | }
5 |
6 | .hero-text {
7 | padding-top: 60px;
8 | font-weight: 400;
9 | text-align: center;
10 | }
11 |
12 | .icon-green {
13 | color: green;
14 | }
15 |
16 | .old-sale-text {
17 | color: rgb(189, 73, 19);
18 | text-decoration: line-through;
19 | text-decoration-color: rgb(189, 73, 19);
20 | }
21 |
22 | /* Use our own button (instead of Stripe's native Checkout Button) */
23 | .stripe-button-el { display: none !important; };
--------------------------------------------------------------------------------
/appname/static/js/main.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/js/main.js
--------------------------------------------------------------------------------
/appname/static/public/fonts/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/public/fonts/.DS_Store
--------------------------------------------------------------------------------
/appname/static/public/fonts/feather/feather-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/public/fonts/feather/feather-webfont.eot
--------------------------------------------------------------------------------
/appname/static/public/fonts/feather/feather-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/public/fonts/feather/feather-webfont.ttf
--------------------------------------------------------------------------------
/appname/static/public/fonts/feather/feather-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/public/fonts/feather/feather-webfont.woff
--------------------------------------------------------------------------------
/appname/static/public/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/public/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/appname/static/public/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/public/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/appname/static/public/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/public/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/appname/static/public/ignite/demo-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/public/ignite/demo-1.png
--------------------------------------------------------------------------------
/appname/static/public/ignite/ignite-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/public/ignite/ignite-icon.png
--------------------------------------------------------------------------------
/appname/static/public/ignite/ignite-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/appname/static/public/ignite/ignite-logo@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/public/ignite/ignite-logo@1x.png
--------------------------------------------------------------------------------
/appname/static/public/ignite/ignite-logo@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/public/ignite/ignite-logo@2x.png
--------------------------------------------------------------------------------
/appname/static/public/ignite/stripe-purchase.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/public/ignite/stripe-purchase.png
--------------------------------------------------------------------------------
/appname/static/public/images/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/public/images/.DS_Store
--------------------------------------------------------------------------------
/appname/static/public/images/browsers/aol-explorer.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/browsers/blackberry.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/browsers/edge.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/browsers/maxthon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/browsers/opera.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/browsers/vivaldi.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/crypto-currencies/bitcoin.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/crypto-currencies/dash.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/crypto-currencies/eos.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/crypto-currencies/ethereum.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/crypto-currencies/litecoin.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/crypto-currencies/ripple.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ae.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ag.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/am.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/at.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/au.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ax.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/az.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ba.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/bb.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/bd.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/be.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/bf.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/bg.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/bh.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/bi.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/bj.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/bl.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/bq.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/bs.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/bv.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/bw.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ca.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/cd.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/cf.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/cg.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ch.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ci.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/cl.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/cm.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/cn.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/co.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/cr.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/cu.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/cv.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/cw.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/cz.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/de.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/dj.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/dk.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/dz.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ee.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/eh.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/et.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/eu.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/fi.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/fm.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/fo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/fr.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ga.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/gb-eng.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/gb-sct.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/gb.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/gf.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/gg.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/gh.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/gl.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/gm.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/gn.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/gp.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/gr.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/gw.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/gy.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/hn.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/hu.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/id.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ie.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/il.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/in.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/is.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/it.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/jm.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/jo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/jp.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ke.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/km.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/kn.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/kp.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/kw.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/la.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/lc.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/lr.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ls.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/lt.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/lu.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/lv.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ly.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ma.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/mc.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/mf.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/mg.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/mh.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/mk.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ml.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/mm.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/mn.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/mq.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/mr.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/mu.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/mv.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/my.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/na.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/nc.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ne.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ng.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/nl.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/no.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/np.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/nr.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/pa.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/pe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ph.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/pk.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/pl.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/pm.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/pr.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ps.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/pw.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/qa.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/re.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ro.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ru.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/rw.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/sb.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/sc.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/sd.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/se.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/sg.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/sj.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/sk.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/sl.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/sn.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/so.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/sr.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ss.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/st.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/sy.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/td.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/tf.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/tg.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/th.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/tk.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/tl.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/tn.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/to.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/tr.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/tt.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/tw.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/tz.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ua.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/uy.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/uz.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/vc.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ve.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/vn.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/wf.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ws.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/ye.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/yt.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/public/images/flags/za.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/appname/static/tabler/fonts/feather/feather-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/tabler/fonts/feather/feather-webfont.eot
--------------------------------------------------------------------------------
/appname/static/tabler/fonts/feather/feather-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/tabler/fonts/feather/feather-webfont.ttf
--------------------------------------------------------------------------------
/appname/static/tabler/fonts/feather/feather-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/tabler/fonts/feather/feather-webfont.woff
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/charts-c3/plugin.js:
--------------------------------------------------------------------------------
1 | require.config({
2 | shim: {
3 | 'c3': ['d3', 'core'],
4 | 'd3': ['core'],
5 | },
6 | paths: {
7 | 'd3': 'assets/plugins/charts-c3/js/d3.v3.min',
8 | 'c3': 'assets/plugins/charts-c3/js/c3.min',
9 | }
10 | });
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/fullcalendar/plugin.js:
--------------------------------------------------------------------------------
1 | require.config({
2 | shim: {
3 | 'fullcalendar': ['moment', 'jquery'],
4 | },
5 | paths: {
6 | 'fullcalendar': 'assets/plugins/fullcalendar/js/fullcalendar.min',
7 | 'moment': 'assets/plugins/fullcalendar/js/moment.min',
8 | }
9 | });
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/iconfonts/fonts/materialdesignicons/materialdesignicons-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/tabler/js/plugins/iconfonts/fonts/materialdesignicons/materialdesignicons-webfont.eot
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/iconfonts/fonts/materialdesignicons/materialdesignicons-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/tabler/js/plugins/iconfonts/fonts/materialdesignicons/materialdesignicons-webfont.ttf
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/iconfonts/fonts/materialdesignicons/materialdesignicons-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/tabler/js/plugins/iconfonts/fonts/materialdesignicons/materialdesignicons-webfont.woff
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/iconfonts/fonts/materialdesignicons/materialdesignicons-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/tabler/js/plugins/iconfonts/fonts/materialdesignicons/materialdesignicons-webfont.woff2
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/iconfonts/fonts/simple-line-icons/Simple-Line-Icons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/tabler/js/plugins/iconfonts/fonts/simple-line-icons/Simple-Line-Icons.eot
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/iconfonts/fonts/simple-line-icons/Simple-Line-Icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/tabler/js/plugins/iconfonts/fonts/simple-line-icons/Simple-Line-Icons.ttf
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/iconfonts/fonts/simple-line-icons/Simple-Line-Icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/tabler/js/plugins/iconfonts/fonts/simple-line-icons/Simple-Line-Icons.woff
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/iconfonts/fonts/simple-line-icons/Simple-Line-Icons.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/tabler/js/plugins/iconfonts/fonts/simple-line-icons/Simple-Line-Icons.woff2
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/iconfonts/fonts/themify/themify.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/tabler/js/plugins/iconfonts/fonts/themify/themify.eot
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/iconfonts/fonts/themify/themify.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/tabler/js/plugins/iconfonts/fonts/themify/themify.ttf
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/iconfonts/fonts/themify/themify.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/tabler/js/plugins/iconfonts/fonts/themify/themify.woff
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/iconfonts/fonts/weathericons/weathericons-regular-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/tabler/js/plugins/iconfonts/fonts/weathericons/weathericons-regular-webfont.eot
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/iconfonts/fonts/weathericons/weathericons-regular-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/tabler/js/plugins/iconfonts/fonts/weathericons/weathericons-regular-webfont.ttf
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/iconfonts/fonts/weathericons/weathericons-regular-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/tabler/js/plugins/iconfonts/fonts/weathericons/weathericons-regular-webfont.woff
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/iconfonts/fonts/weathericons/weathericons-regular-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/tabler/js/plugins/iconfonts/fonts/weathericons/weathericons-regular-webfont.woff2
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/input-mask/plugin.js:
--------------------------------------------------------------------------------
1 | require.config({
2 | shim: {
3 | 'input-mask': ['jquery', 'core']
4 | },
5 | paths: {
6 | 'input-mask': 'assets/plugins/input-mask/js/jquery.mask.min'
7 | }
8 | });
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/maps-google/plugin.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/tabler/js/plugins/maps-google/plugin.css
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/maps-google/plugin.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/static/tabler/js/plugins/maps-google/plugin.js
--------------------------------------------------------------------------------
/appname/static/tabler/js/plugins/prismjs/plugin.js:
--------------------------------------------------------------------------------
1 | require.config({
2 | paths: {
3 | 'prismjs': 'assets/plugins/prismjs/js/prism.pack',
4 | },
5 | shim: {
6 | prism: {
7 | exports: "Prism"
8 | }
9 | }
10 | });
11 |
12 | require(['prismjs', 'jquery'], function(prismjs, $){
13 | $(document).ready(function(){
14 | // $('[class^="language-"]').each(function(i, block) {
15 | // Prism.highlightElement(block);
16 | // });
17 | });
18 | });
--------------------------------------------------------------------------------
/appname/templates/admin/index.html:
--------------------------------------------------------------------------------
1 | {% extends admin_base_template %}
2 |
3 | {% block body %}
4 |
5 |
6 |
Welcome to {{branding.name}} admin panel
7 |
8 | View all Users
9 |
10 |
11 |
12 |
13 |
14 |
{{ user_count }}
15 | Number of Users
16 |
17 |
18 |
{{ team_count }}
19 | Number of Teams
20 |
21 |
22 |
{{ paid_count }}
23 | Number of Paid Plans
24 |
25 |
26 |
27 | {% endblock %}
28 |
--------------------------------------------------------------------------------
/appname/templates/auth/reauth.html:
--------------------------------------------------------------------------------
1 | {% extends "tabler/minimal_base.html" %}
2 | {% from "helpers/_formhelpers.html" import render_field %}
3 |
4 | {% block title %}{{branding.name}} Login Example{% endblock %}
5 |
6 | {% block body %}
7 |
8 |
9 |
10 |
11 |
12 |
Confirm your login in order to procceed
13 |
17 |
18 |
19 |
20 |
21 |
22 | {% endblock %}
23 |
--------------------------------------------------------------------------------
/appname/templates/auth/request_password_reset.html:
--------------------------------------------------------------------------------
1 | {% extends "tabler/minimal_base.html" %}
2 | {% from "helpers/_formhelpers.html" import render_field %}
3 |
4 | {% block title %}{{branding.name}} Login Example{% endblock %}
5 |
6 | {% block body %}
7 |
8 |
9 |
10 |
11 |
12 | {{ render_flashes() }}
13 |
 }})
14 |
15 |
16 |
31 |
32 |
33 |
34 |
35 |
36 | {% include 'tabler/_footer.html' with context %}
37 |
38 | {% endblock %}
39 |
40 |
--------------------------------------------------------------------------------
/appname/templates/auth/resend_confirmation.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% from "helpers/_formhelpers.html" import render_field %}
3 |
4 | {% block title %}{{branding.name}} Login {% endblock %}
5 |
6 | {% block body %}
7 | Resend Confirmation Email
8 |
9 |
10 |
11 |
12 |
13 |
14 | The message may end up in your Spam Inbox
15 |
16 |
20 |
21 |
22 |
23 | {% endblock %}
--------------------------------------------------------------------------------
/appname/templates/auth/reset_password.html:
--------------------------------------------------------------------------------
1 | {% extends "tabler/minimal_base.html" %}
2 | {% from "helpers/_formhelpers.html" import render_field %}
3 |
4 | {% block title %}{{branding.name}} Reset Password{% endblock %}
5 |
6 | {% block body %}
7 |
8 |
9 |
10 |
11 |
12 | {{ render_flashes() }}
13 |
 }})
14 |
15 |
16 |
28 |
29 |
30 |
31 |
32 |
33 | {% include 'tabler/_footer.html' with context %}
34 |
35 | {% endblock %}
36 |
37 |
--------------------------------------------------------------------------------
/appname/templates/email/confirm_email.html:
--------------------------------------------------------------------------------
1 | {% extends "email/base.html" %}
2 |
3 | {% block body %}
4 | Confirm your email address on {{branding.name}}
5 |
6 |
Click the button below to confirm your email address
7 |
8 |
9 |
10 |
17 |
18 |
19 |
20 | Or copy and paste this address into your browser: {{link}}
21 |
22 | {% endblock %}
--------------------------------------------------------------------------------
/appname/templates/email/notification.html:
--------------------------------------------------------------------------------
1 | {% extends "email/base.html" %}
2 |
3 | {% block body %}
4 | {{branding.name}}
5 |
6 | {{ body}}
7 |
8 | {% if link and link_text %}
9 |
10 |
11 |
18 |
19 |
20 | {% endif %}
21 |
22 | {% endblock %}
--------------------------------------------------------------------------------
/appname/templates/email/purchase_receipt.html:
--------------------------------------------------------------------------------
1 | {% extends "email/base.html" %} {% block body %}
2 | Ignite Premium
3 |
4 | Thanks for your purchase of Ignite. We will follow up within 3 days with a link to the souce code
5 |
6 | Your license code is
{{ license }}
7 |
8 | The terms of the license are included below
9 |
10 | A license grants you a non-exclusive and non-transferable right to use and incorporate the item in your commercial
11 | projects.
12 |
13 |
14 | - Your use of the item is restricted to a single application.
15 | - You may use the item in work which you are creating for your own purposes or for your client.
16 | - You must not incorporate the item in a work which is created for redistribution or resale by you or your client.
17 | - The item may not be redistributed or resold.
18 | - You are subject to the licenses for sub-components (such as Bootstrap) used within the project.
19 |
20 |
21 | Thanks again
22 |
23 | {% endblock %}
24 |
--------------------------------------------------------------------------------
/appname/templates/email/reset_password.html:
--------------------------------------------------------------------------------
1 | {% extends "email/base.html" %}
2 |
3 | {% block body %}
4 | Reset your password
5 |
6 | Someone (hopefully you) asked us to reset your password on {{branding.name}}.
7 | Click the link below to reset your password.
8 |
9 |
10 |
11 |
18 |
19 |
20 |
21 |
22 | Or copy and paste this address into your browser: {{link}}
23 |
24 | Wasn't you? Let us know
25 |
26 | {% endblock %}
--------------------------------------------------------------------------------
/appname/templates/email/teams/invite.html:
--------------------------------------------------------------------------------
1 | {% extends "email/base.html" %}
2 |
3 | {% block body %}
4 | You've been invited to {{ invite.team.name }}
5 |
6 | {{ invite.inviter.email }} has invited you to join their team on {{branding.name}}.
7 |
8 | Click the button below to complete your setup
9 |
10 |
11 |
12 |
19 |
20 |
21 |
22 | Or copy and paste this address into your browser: {{link}}
23 |
24 | {% endblock %}
--------------------------------------------------------------------------------
/appname/templates/helpers/_flashes.html:
--------------------------------------------------------------------------------
1 |
2 | {% macro render_flashes() %}
3 | {% with messages = get_flashed_messages(with_categories=true) %}
4 | {% if messages %}
5 |
6 | {% for category, message in messages %}
7 |
8 |
10 | {{ message }}
11 |
12 | {% endfor %}
13 |
14 | {% endif %}
15 | {% endwith %}
16 | {% endmacro %}
17 |
--------------------------------------------------------------------------------
/appname/templates/helpers/_formhelpers.html:
--------------------------------------------------------------------------------
1 | {% macro render_field(field, class="form-control", label_hidden=false) %}
2 |
16 | {% endmacro %}
17 |
--------------------------------------------------------------------------------
/appname/templates/settings/account.html:
--------------------------------------------------------------------------------
1 | {% extends "tabler/dashboard_base.html" %}
2 | {% from "helpers/_formhelpers.html" import render_field %}
3 |
4 | {% block title %}{{branding.name}} account settings{% endblock %}
5 |
6 | {% block body %}
7 |
8 |
9 |
10 |
11 |
12 | {% include 'settings/sidebar.html' %}
13 |
14 |
32 |
33 |
34 |
35 |
36 | {% endblock %}
37 |
--------------------------------------------------------------------------------
/appname/templates/settings/change_password.html:
--------------------------------------------------------------------------------
1 | {% extends "tabler/dashboard_base.html" %}
2 | {% from "helpers/_formhelpers.html" import render_field %}
3 |
4 | {% block title %}{{branding.name}} settings{% endblock %}
5 |
6 | {% block body %}
7 |
8 |
9 |
10 |
11 |
12 | {% include 'settings/sidebar.html' %}
13 |
14 |
33 |
34 |
35 |
36 |
37 | {% endblock %}
38 |
--------------------------------------------------------------------------------
/appname/templates/tabler/401.html:
--------------------------------------------------------------------------------
1 | {% extends "tabler/minimal_base.html" %}
2 | {% block title %}{{branding.name}} error - 401{% endblock %}
3 |
4 | {% block js %}
5 |
6 | {% endblock %}
7 |
8 | {% block body %}
9 |
10 |
11 |
401
12 |
Oops.. You just found an error page..
13 |
We are sorry but you are not authorized to access this page…
14 |
15 | Go back
16 |
17 |
18 |
19 | {% endblock %}
20 |
21 |
--------------------------------------------------------------------------------
/appname/templates/tabler/_footer.html:
--------------------------------------------------------------------------------
1 |
27 |
--------------------------------------------------------------------------------
/appname/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/appname/utils/__init__.py
--------------------------------------------------------------------------------
/appname/utils/math.py:
--------------------------------------------------------------------------------
1 | def ceildiv(a, b):
2 | return -(-a // b)
3 |
4 | def chunks(l, n):
5 | """ Divides L into N many chunks, each containing approximately the
6 | same number of elements
7 | Refrence: http://stackoverflow.com/a/9873935
8 | >>> [len(x) for x in chunks(range(45), 13)]
9 | [4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 3]
10 | >>> [len(x) for x in chunks(range(253), 13)]
11 | [20, 19, 20, 19, 20, 19, 20, 19, 20, 19, 20, 19, 19]
12 | >>> [len(i) for i in chunks(range(56), 3)]
13 | [19, 19, 18]
14 | >>> [len(i) for i in chunks(range(55), 5)]
15 | [11, 11, 11, 11, 11]
16 | """
17 | length = len(l)
18 | prev_index = 0
19 | for i in range(1, n + 1):
20 | index = ceildiv(i * length, n)
21 | yield l[prev_index:index]
22 | prev_index = index
23 |
--------------------------------------------------------------------------------
/appname/utils/text.py:
--------------------------------------------------------------------------------
1 | def pluralize(number, singular='', plural='s'):
2 | """ Pluralize filter for Jinja.
3 | Source: http://stackoverflow.com/a/22336061/411514
4 | """
5 | if number == 1:
6 | return singular
7 | return plural
8 |
--------------------------------------------------------------------------------
/appname/utils/time.py:
--------------------------------------------------------------------------------
1 | import datetime as dt
2 | import pytz
3 |
4 | # Timezones. Be cautious with using tzinfo argument. http://pytz.sourceforge.net/
5 | # "tzinfo argument of the standard datetime constructors 'does not work'
6 | # with pytz for many timezones."
7 |
8 | def local_time(time, course, fmt='%a %m/%d %I:%M %p'):
9 | """ Format a time string in a course's locale.
10 | Note that %-I does not perform as expected on Alpine Linux
11 | """
12 | return local_time_obj(time, course).strftime(fmt)
13 |
14 | def local_time_obj(time, locale):
15 | """ Get a Datetime object in a locale from a TZ Aware DT object."""
16 | if not time.tzinfo:
17 | time = pytz.utc.localize(time)
18 | return time.astimezone(locale)
19 |
20 | def server_time_obj(time, locale):
21 | """ Convert a datetime object from a locale to a UTC
22 | datetime object.
23 | """
24 | if not time.tzinfo:
25 | time = locale.localize(time)
26 | # Store using UTC on the server side.
27 | return time.astimezone(pytz.utc)
28 |
29 | def future_time_obj(locale, **kwargs):
30 | """ Get a datetime object representing some timedelta from now with the time
31 | set at 23:59:59.
32 | """
33 | date = locale.localize(dt.datetime.now() + dt.timedelta(**kwargs))
34 | time = dt.time(hour=23, minute=59, second=59, microsecond=0)
35 | return dt.datetime.combine(date, time)
36 |
--------------------------------------------------------------------------------
/appname/utils/token.py:
--------------------------------------------------------------------------------
1 | import base64
2 | import uuid
3 |
4 | # Python 3.6+ would allow us to do
5 | # >>> import secrets
6 | # >>> secrets.token_urlsafe(8)
7 | # 'Pxym7N2zJRs'
8 |
9 | def url_safe_token():
10 | base64token = base64.urlsafe_b64encode(uuid.uuid4().bytes)
11 | return (base64token.decode('utf-8').replace('=', '').
12 | replace('-', '').replace('_', '')[0:9])
13 |
14 | def generate_api_secret(user):
15 | base64token = base64.urlsafe_b64encode(uuid.uuid4().bytes)
16 | user_secret = (base64token.decode('utf-8').replace('=', '').replace('-', '').replace('_', ''))
17 | api_key = "{}-{}".format(user.hashid, user_secret)
18 | return api_key
19 |
--------------------------------------------------------------------------------
/docker/.dockerignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.pyo
3 | *.swp
4 | *~
5 |
6 | # Packages
7 | *.egg
8 | *.egg-info
9 | dist
10 | build/
11 | eggs
12 | bin
13 | sdist
14 |
15 | #Sphinx builds
16 | _build
17 |
18 | # Installer logs
19 | pip-log.txt
20 |
21 | # Flake8 violation file
22 | violations.flake8.txt
23 |
24 | # Unit test / coverage reports
25 | .coverage
26 | .tox
27 | nosetests.xml
28 | __pycache__
29 | database.db
30 |
31 | # Sphinx
32 | docs/_build
33 |
34 | # Virtual environments
35 | env
36 | env*
37 |
38 | # Env Vars
39 | *.env*
40 |
41 | .webassets-cache
42 | *.cache
43 | .DS_Store
44 | *.sublime-*
45 |
46 | appname/static/public/*
47 |
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3
2 |
3 | RUN apk add --update ca-certificates;
4 |
5 | RUN mkdir -p /usr/src/app
6 | WORKDIR /usr/src/app
7 | ONBUILD RUN pip install --no-cache-dir -r requirements.txt
8 | ONBUILD COPY . /usr/src/app
9 |
10 | CMD gunicorn -w 0.0.0.0:5000 wsgi:app
11 |
12 | RUN rm -rf /var/cache/apk/*
13 |
14 | EXPOSE 5000
15 |
--------------------------------------------------------------------------------
/documentation/TODO.md:
--------------------------------------------------------------------------------
1 | Core Functionality:
2 |
3 | - [x] API
4 | - [x] User Sign Up
5 | - [x] Login via OAuth (Google|Facebook|Etc)
6 | - [x] Change Password
7 | - [x] Email Sending
8 | - [x] User Email Confirmations
9 | - [x] Reset Password Email
10 | - [x] User Dashboard
11 | - [x] Admin View
12 | - [x] Good UI Template
13 | - [x] Basic Test Suite
14 | - [x] Delayed Jobs/Work Queue
15 | - [x] Migrations
16 | - [x] Team Memberships & Invite Management
17 | - [x] GDPR Export Features
18 | - [x] Recurring SaaS Billing
19 | - [x] Landing Page
20 |
21 | Nice to haves:
22 | - [x] VSCode Debugger Integration
23 | - [x] One off payments via Stripe
24 | - [x] Generate API tokens for user
25 | - [x] File Storage/Upload
26 | - [ ] Two Factor Auth
27 | - [ ] Example of JS Framework Integration
28 | - [ ] Cleanup auth methods (use a decorator)
29 | - [ ] Use a custom URL route for team
30 |
31 | Maybe:
32 | - [ ] OAuth Provider Code (easy to add, if there is demand)
33 |
--------------------------------------------------------------------------------
/documentation/design/tabler.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/documentation/design/tabler.sketch
--------------------------------------------------------------------------------
/documentation/gdpr/README.md:
--------------------------------------------------------------------------------
1 |
2 | 
3 |
4 | You should contact your attorney to obtain advice with respect to compliance or any specific issue. This information (along with any of the content in this repository) is not legal advice & should not be relied upon as legal advice. The information, defaults, and functionality in the code provided should be considered informational.
5 |
6 |
--------------------------------------------------------------------------------
/documentation/oauth.md:
--------------------------------------------------------------------------------
1 | OAuth is backed by Flask-Dance
2 |
3 | ## Setup
4 |
5 | ## Providers
6 |
7 | ### Google
8 |
9 | #### Configuration:
10 |
11 | * Get API Keys here: https://console.developers.google.com/apis/credentials
12 | * Add "http://localhost:5000/oauth/google/authorized" and "https://[your production website]/oauth/google/authorized" to the authorized redirects
13 |
14 | Make sure you set the following environment variables so that you can try this locally.
15 |
16 | ```
17 | export OAUTHLIB_INSECURE_TRANSPORT=1 # To allow local logins without having to use HTTPs locally
18 | export GOOGLE_CONSUMER_KEY='your-code.apps.googleusercontent.com'
19 | export GOOGLE_CONSUMER_SECRET='your-secret'
20 | ```
21 |
22 | #### Implementation:
23 |
24 | See `appname/controllers/google.py`.
25 |
26 |
27 | ### Other Providers (Github, Slack, Twitter, etc)
28 |
29 | Add similiar blueprints for each one. Reference the [Flask-Dance](https://flask-dance.readthedocs.io/en/latest/) documentation. We use the SQLAlchemy storage backend.
30 |
31 | ### Custom Provider
32 | ```
33 | from flask import Flask
34 | from flask_dance.consumer import OAuth2ConsumerBlueprint
35 |
36 | example_blueprint = OAuth2ConsumerBlueprint(
37 | "ok-server", __name__,
38 | client_id="my-key-here",
39 | client_secret="my-secret-here",
40 | base_url="https://okpy.org/api/v3/",
41 | token_url="https://okpy.org/oauth/token",
42 | authorization_url="https://okpy.org/oauth/authorize,
43 | scope='email',
44 | # state=lambda: security.gen_salt(10),
45 |
46 | # The rest would be similiar to `google.py` but you'd have to update the API endpoints.
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/documentation/screenshots/admin-home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/documentation/screenshots/admin-home.png
--------------------------------------------------------------------------------
/documentation/screenshots/admin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/documentation/screenshots/admin.png
--------------------------------------------------------------------------------
/documentation/screenshots/api-json.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/documentation/screenshots/api-json.png
--------------------------------------------------------------------------------
/documentation/screenshots/api.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/documentation/screenshots/api.png
--------------------------------------------------------------------------------
/documentation/screenshots/billing-sucess.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/documentation/screenshots/billing-sucess.png
--------------------------------------------------------------------------------
/documentation/screenshots/billing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/documentation/screenshots/billing.png
--------------------------------------------------------------------------------
/documentation/screenshots/dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/documentation/screenshots/dashboard.png
--------------------------------------------------------------------------------
/documentation/screenshots/email.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/documentation/screenshots/email.png
--------------------------------------------------------------------------------
/documentation/screenshots/file-uploads.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/documentation/screenshots/file-uploads.png
--------------------------------------------------------------------------------
/documentation/screenshots/gdpr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/documentation/screenshots/gdpr.png
--------------------------------------------------------------------------------
/documentation/screenshots/jobs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/documentation/screenshots/jobs.png
--------------------------------------------------------------------------------
/documentation/screenshots/login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/documentation/screenshots/login.png
--------------------------------------------------------------------------------
/documentation/screenshots/oauth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/documentation/screenshots/oauth.png
--------------------------------------------------------------------------------
/documentation/screenshots/reset-pw-submit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/documentation/screenshots/reset-pw-submit.png
--------------------------------------------------------------------------------
/documentation/screenshots/reset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/documentation/screenshots/reset.png
--------------------------------------------------------------------------------
/documentation/screenshots/signup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/documentation/screenshots/signup.png
--------------------------------------------------------------------------------
/documentation/screenshots/stripe-charge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/documentation/screenshots/stripe-charge.png
--------------------------------------------------------------------------------
/documentation/screenshots/stripe-console.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/documentation/screenshots/stripe-console.png
--------------------------------------------------------------------------------
/documentation/screenshots/team.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/documentation/screenshots/team.png
--------------------------------------------------------------------------------
/documentation/stripe.md:
--------------------------------------------------------------------------------
1 | # Setting up Stripe
2 |
3 |
4 | ## Signing up on Stripe.com
5 |
6 | Sign up for Stripe and get your development API keys. Insert them in to your `.env.local` file.
7 |
8 | ## Setting up recurring billing
9 |
10 | Enable the "Customer Portal" on Stripe.com.
11 |
12 | Create two or three recurring billing plans, then configure them in `billing_plans.py`
13 |
--------------------------------------------------------------------------------
/migrations/README:
--------------------------------------------------------------------------------
1 | Generic single-database configuration.
--------------------------------------------------------------------------------
/migrations/alembic.ini:
--------------------------------------------------------------------------------
1 | # A generic, single database configuration.
2 |
3 | [alembic]
4 | # template used to generate migration files
5 | # file_template = %%(rev)s_%%(slug)s
6 |
7 | # set to 'true' to run the environment during
8 | # the 'revision' command, regardless of autogenerate
9 | # revision_environment = false
10 |
11 |
12 | # Logging configuration
13 | [loggers]
14 | keys = root,sqlalchemy,alembic
15 |
16 | [handlers]
17 | keys = console
18 |
19 | [formatters]
20 | keys = generic
21 |
22 | [logger_root]
23 | level = WARN
24 | handlers = console
25 | qualname =
26 |
27 | [logger_sqlalchemy]
28 | level = WARN
29 | handlers =
30 | qualname = sqlalchemy.engine
31 |
32 | [logger_alembic]
33 | level = INFO
34 | handlers =
35 | qualname = alembic
36 |
37 | [handler_console]
38 | class = StreamHandler
39 | args = (sys.stderr,)
40 | level = NOTSET
41 | formatter = generic
42 |
43 | [formatter_generic]
44 | format = %(levelname)-5.5s [%(name)s] %(message)s
45 | datefmt = %H:%M:%S
46 |
--------------------------------------------------------------------------------
/migrations/script.py.mako:
--------------------------------------------------------------------------------
1 | """${message}
2 |
3 | Revision ID: ${up_revision}
4 | Revises: ${down_revision | comma,n}
5 | Create Date: ${create_date}
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 | import sqlalchemy_utils
11 | ${imports if imports else ""}
12 |
13 | # revision identifiers, used by Alembic.
14 | revision = ${repr(up_revision)}
15 | down_revision = ${repr(down_revision)}
16 | branch_labels = ${repr(branch_labels)}
17 | depends_on = ${repr(depends_on)}
18 |
19 |
20 | def upgrade():
21 | ${upgrades if upgrades else "pass"}
22 |
23 |
24 | def downgrade():
25 | ${downgrades if downgrades else "pass"}
26 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Flask==2.2.5
2 | jinja2==3.1.4
3 | Werkzeug~=2.2.3
4 |
5 | requests==2.32.3
6 | gunicorn
7 |
8 | cryptography==42.0.8
9 |
10 | # Flask Extensions
11 | Flask-Assets>=0.12,<2.2.0
12 |
13 | # Branch that contains a Unicode bug fix - use until https://github.com/miracle2k/webassets/pull/482 is published
14 | webassets==2.0
15 |
16 | Flask-Caching>=1.3.3
17 | Flask-Login==0.6.3
18 | Flask-DebugToolbar==0.13.1
19 | Flask-Script==2.0.6
20 | Flask-WTF==1.0.1
21 | wtforms[email]
22 |
23 | # OAuth
24 | Flask-Dance[sqla]
25 |
26 |
27 | Flask-RESTful==0.3.10
28 | Flask-Testing>=0.6.2,<1.0
29 |
30 | Flask-SocketIO>=3.1.0 # Realtime Websockets
31 | python-engineio>=3.0.0 # Needed to fix startup error
32 |
33 | Flask-Mail
34 | Flask-Limiter==2.6.3
35 |
36 | # Admin
37 | Flask-Admin>=1.5.6
38 |
39 | # Database
40 | pymysql==1.1.1
41 | psycopg2-binary
42 | sqlalchemy==1.4.42
43 | sqlalchemy-utils==0.41.2 # Provides extra datatypes
44 |
45 | # Timezones
46 | pytz
47 | arrow
48 |
49 | # Email
50 | pynliner<1.0
51 |
52 | # File Storage
53 | flask-cloudy
54 | fasteners
55 |
56 | # Flask DB Extensions
57 | Flask-SQLAlchemy==2.5.1
58 | Flask-SQLAlchemy-Cache==0.1.5
59 | Flask-Migrate==2.7.0
60 |
61 | # Caching
62 | redis==4.4.4
63 |
64 | # Job Queue
65 | Flask-RQ2==18.3
66 | rq==1.11.1
67 | rq-dashboard==0.6.1
68 |
69 | # Billing
70 | stripe==2.76.0
71 |
72 | # Error Tracking
73 | raven[flask]
74 |
75 | # Other
76 | itsdangerous==2.2.0
77 | cssmin==0.2.0
78 | jsmin==3.0.1
79 | hashids==1.3.1
80 | humanize==4.4.0
81 |
82 | # Testing
83 | pytest==8.2.0
84 | pytest-cov==4.0.0
85 | mccabe==0.7.0
86 | flake8==6.0.0
87 |
88 |
--------------------------------------------------------------------------------
/runtime.txt:
--------------------------------------------------------------------------------
1 | python-3.9.9
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [pep8]
2 | max-line-length = 120
3 | ignore = E302,E123,E133,E226,E241,E242
4 | exclude = tests/*,.git,env,migrations/*
5 |
6 | [flake8]
7 | ignore = C0111,I0011,I0012,E302,E123,E133,E226,E241,E242
8 | max-line-length = 120
9 | exclude = tests/*,.git,env,migrations/*
10 | max-complexity = 10
11 | import-order-style = pep8
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sumukh/Ignite/9c75ce740e2d189d79cb27d444e76ffa61296908/tests/__init__.py
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from appname import create_app
4 | from appname.models import db
5 | from appname.models.user import User
6 |
7 | @pytest.fixture()
8 | def testapp(request):
9 | app = create_app('appname.settings.TestConfig')
10 | client = app.test_client()
11 |
12 | db.app = app
13 | db.create_all()
14 |
15 | if getattr(request.module, "create_user", True):
16 | admin = User('admin@example.com', 'supersafepassword', admin=True)
17 | user = User('user@example.com', 'safepassword')
18 | db.session.add_all([admin, user])
19 | db.session.commit()
20 |
21 | def teardown():
22 | db.session.remove()
23 | db.drop_all()
24 |
25 | request.addfinalizer(teardown)
26 |
27 | return client
28 |
--------------------------------------------------------------------------------
/tests/test_config.py:
--------------------------------------------------------------------------------
1 | from appname import create_app
2 | import os
3 |
4 | class TestConfig:
5 | def test_dev_config(self):
6 | """ Tests if the development config loads correctly """
7 |
8 | app = create_app('appname.settings.DevConfig')
9 |
10 | assert app.config['DEBUG'] is True
11 | assert app.config['SQLALCHEMY_DATABASE_URI'] == 'sqlite:///../database.db'
12 | assert app.config['CACHE_TYPE'] == 'simple'
13 |
14 | def test_test_config(self):
15 | """ Tests if the test config loads correctly """
16 |
17 | app = create_app('appname.settings.TestConfig')
18 |
19 | assert app.config['DEBUG'] is True
20 | assert app.config['CACHE_TYPE'] == 'null'
21 |
22 | def test_prod_config(self):
23 | """ Tests if the production config loads correctly """
24 | app = create_app('appname.settings.ProdConfig')
25 | assert app.config['DEBUG'] is False
26 |
27 | assert app.config['CACHE_TYPE'] == 'redis'
28 |
--------------------------------------------------------------------------------
/tests/test_login.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | create_user = True
4 |
5 | @pytest.mark.usefixtures("testapp")
6 | class TestLogin:
7 | def test_login(self, testapp):
8 | """ Tests if the login form functions """
9 |
10 | rv = testapp.post('/login', data=dict(
11 | email='admin@example.com',
12 | password="supersafepassword"
13 | ), follow_redirects=True)
14 |
15 | assert rv.status_code == 200
16 | assert 'Logged in successfully.' in str(rv.data)
17 |
18 | def test_login_bad_email(self, testapp):
19 | """ Tests if the login form rejects invalid email """
20 |
21 | rv = testapp.post('/login', data=dict(
22 | email='admin',
23 | password=""
24 | ), follow_redirects=True)
25 |
26 | assert rv.status_code == 200
27 | assert 'Invalid email address' in str(rv.data)
28 |
29 | def test_login_bad_password(self, testapp):
30 | """ Tests if the login form fails correctly """
31 |
32 | rv = testapp.post('/login', data=dict(
33 | email='admin@example.com',
34 | password="notsafepassword"
35 | ), follow_redirects=True)
36 |
37 | assert rv.status_code == 200
38 | assert 'Invalid email or password' in str(rv.data)
39 |
--------------------------------------------------------------------------------
/tests/test_team.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from appname.models import db
4 | from appname.models.user import User
5 | from appname.models.teams.team import Team
6 |
7 | create_user = False
8 |
9 | @pytest.mark.usefixtures("testapp")
10 | class TestTeam:
11 | def test_user_creation_of_team(self, testapp):
12 | """ Test that creating a user, creates a group & a membership """
13 | user = User('user2@example.com', 'supersafepassword')
14 | db.session.add(user)
15 | db.session.commit()
16 |
17 | assert len(user.memberships) == 1
18 | membership = user.memberships[0]
19 | assert membership.activated
20 | assert membership.role == 'administrator'
21 | assert membership.team.creator == user
22 |
--------------------------------------------------------------------------------
/tests/test_urls.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | create_user = True
4 |
5 | def expect_response(route, code, testapp):
6 | rv = testapp.get(route)
7 | assert rv.status_code == code
8 |
9 | @pytest.mark.usefixtures("testapp")
10 | class TestURLs:
11 |
12 | def test_home(self, testapp):
13 | """ Tests if the home page loads """
14 | expect_response('/', 200, testapp)
15 |
16 | def test_login(self, testapp):
17 | """ Tests if the login page loads """
18 | expect_response('/login', 200, testapp)
19 |
20 | def test_logout(self, testapp):
21 | """ Tests if the logout page redirects """
22 | expect_response('/auth/logout', 302, testapp)
23 |
24 | def test_signup(self, testapp):
25 | """ Tests if the logout page loads """
26 | expect_response('/signup', 200, testapp)
27 |
28 | def test_dashboard_logged_out(self, testapp):
29 | """ Tests if the restricted page returns a 302
30 | if the user is logged out
31 | """
32 | expect_response('/dashboard/', 302, testapp)
--------------------------------------------------------------------------------
/tmp/.touch:
--------------------------------------------------------------------------------
1 | # Temporarily creating a file here so that libcloud doesn't delete this directory
2 |
--------------------------------------------------------------------------------
/wsgi.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """
3 | For WSGI Server
4 | To run:
5 | $ gunicorn -b 0.0.0.0:5000 wsgi:app
6 | OR
7 | $ export FLASK_APP=wsgi
8 | $ flask run
9 | """
10 | import os
11 | from appname import create_app
12 |
13 | env = os.environ.get('APPNAME_ENV', 'dev')
14 | app = create_app('appname.settings.%sConfig' % env.capitalize())
15 |
--------------------------------------------------------------------------------