├── .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 | 3 | 4 | ignite-icon 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /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 | blackberryCreated with Sketch.Layer 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 | Litecoin -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | 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 | 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 | 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 |
17 | {{ form.hidden_tag() }} 18 | 19 |
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 | 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 | 11 | 12 | 15 | 16 |
13 | Confirm your email 14 |
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 | 12 | 13 | 16 | 17 |
14 | {{link_text}} 15 |
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 |
  1. Your use of the item is restricted to a single application.
  2. 15 |
  3. You may use the item in work which you are creating for your own purposes or for your client.
  4. 16 |
  5. You must not incorporate the item in a work which is created for redistribution or resale by you or your client.
  6. 17 |
  7. The item may not be redistributed or resold.
  8. 18 |
  9. You are subject to the licenses for sub-components (such as Bootstrap) used within the project.
  10. 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 | 12 | 13 | 16 | 17 |
14 | Reset password 15 |
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 | 13 | 14 | 17 | 18 |
15 | Join {{ invite.inviter.email }} 16 |
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 | 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 |
3 | {% if not label_hidden %} 4 | {{ field.label }} 5 | {% if field.errors %} 6 | {% for e in field.errors %} 7 |

{{ e }}

8 | {% endfor %} 9 | {% endif %} 10 | {% if field.description %} 11 |

{{ field.description }} 12 | {% endif %} 13 | {% endif %} 14 | {{ field(class_=class, **kwargs) | safe }} 15 |

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 |
15 |
16 |
17 |

Profile Information

18 |
19 |
20 |
21 | {{ form.hidden_tag() }} 22 | {{ render_field(form.name, value=(current_user.full_name or ''), placeholder='Your Name') }} 23 |
24 | 29 |
30 |
31 |
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 |
15 |
16 |
17 |

Change Password

18 |
19 |
20 |
21 | {{ form.hidden_tag() }} 22 | {{ render_field(form.password) }} 23 | {{ render_field(form.confirm) }} 24 |
25 | 30 |
31 |
32 |
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 | ![Resources on GDPR](https://github.com/erichard/awesome-gdpr) 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 | --------------------------------------------------------------------------------