├── .github └── workflows │ └── google.yml ├── .gitignore ├── LICENSE ├── README.md ├── app-data └── db.sqlite3 ├── backend ├── .dockerignore ├── .gitignore ├── .vscode │ └── settings.json ├── Dockerfile ├── backend │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── custom_exception_handler.py │ ├── management │ │ └── commands │ │ │ └── seed.py │ ├── messages.py │ ├── middlewares.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── pagination.py │ ├── permissions.py │ ├── serializers.py │ ├── shortcuts.py │ ├── templates │ │ ├── mail_buy_interest.html │ │ ├── mail_contact_us.html │ │ ├── mail_demo_interest.html │ │ ├── mail_discount_interest.html │ │ ├── mail_generic.html │ │ ├── mail_inquiry_interest.html │ │ ├── mail_meeting.html │ │ ├── mail_reset_password_verify.html │ │ ├── mail_sign_up_verify.html │ │ └── mail_suggested_feautre.html │ ├── tests │ │ ├── __init__.py │ │ ├── test_api.py │ │ └── test_email.py │ ├── urls.py │ ├── utils │ │ ├── data.py │ │ ├── email_templates.py │ │ ├── exceptions.py │ │ ├── hash_password.py │ │ ├── ipinfo.py │ │ ├── jwt.py │ │ ├── model_utils.py │ │ ├── send_email.py │ │ └── utils.py │ ├── validators.py │ └── views │ │ ├── __init__.py │ │ ├── auth.py │ │ ├── ipinfo.py │ │ ├── send_email.py │ │ ├── stats.py │ │ ├── test.py │ │ ├── url_shortener.py │ │ └── users.py ├── config │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ ├── views │ │ └── downloads.py │ └── wsgi.py ├── downloads │ ├── banner.png │ └── logo.png ├── http │ └── index.http ├── manage.py ├── package.json └── requirements.txt ├── blog ├── .env.example ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .husky │ ├── _ │ │ └── husky.sh │ └── pre-commit ├── Dockerfile ├── README.md ├── components │ ├── Card.tsx │ ├── Footer.tsx │ ├── Header.tsx │ ├── Image.tsx │ ├── LayoutWrapper.tsx │ ├── Link.tsx │ ├── MDXComponents.tsx │ ├── MobileNav.tsx │ ├── PageTitle.tsx │ ├── SEO.tsx │ ├── ScrollTopAndComment.tsx │ ├── SectionContainer.tsx │ ├── Tag.tsx │ ├── ThemeSwitch.tsx │ └── social-icons │ │ ├── facebook.svg │ │ ├── github.svg │ │ ├── index.tsx │ │ ├── linkedin.svg │ │ ├── mail.svg │ │ ├── twitter.svg │ │ └── youtube.svg ├── contentlayer.config.ts ├── css │ ├── docsearch.css │ ├── prism.css │ └── tailwind.css ├── data │ ├── authors │ │ └── default.mdx │ ├── blog │ │ └── youtube-seo-tips.mdx │ ├── headerNavLinks.ts │ ├── logo.svg │ ├── projectsData.ts │ ├── references-data.bib │ └── siteMetadata.js ├── jsconfig.json ├── layouts │ ├── AuthorLayout.tsx │ ├── ListLayout.tsx │ ├── PostLayout.tsx │ └── PostSimple.tsx ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pages │ ├── 404.tsx │ ├── [...slug].tsx │ ├── _app.tsx │ ├── _document.tsx │ ├── about.tsx │ ├── api │ │ ├── newsletter.ts │ │ └── posts.ts │ ├── index.tsx │ ├── page │ │ └── [page].tsx │ ├── projects.tsx │ ├── tags.tsx │ └── tags │ │ └── [tag].tsx ├── postcss.config.js ├── prettier.config.js ├── public │ └── static │ │ ├── blog │ │ └── youtube-seo-tips │ │ │ └── youtube-seo-tips.png │ │ ├── favicons │ │ ├── android-chrome-96x96.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── favicon.ico │ │ ├── mstile-150x150.png │ │ ├── safari-pinned-tab.svg │ │ └── site.webmanifest │ │ └── images │ │ ├── avatar.png │ │ ├── canada │ │ ├── lake.jpg │ │ ├── maple.jpg │ │ ├── mountains.jpg │ │ └── toronto.jpg │ │ ├── default.jpg │ │ ├── google.png │ │ ├── logo.png │ │ ├── ocean.jpeg │ │ ├── sparrowhawk-avatar.jpg │ │ ├── time-machine.jpg │ │ └── twitter-card.png ├── scripts │ ├── postbuild.mjs │ └── search.mjs ├── tailwind.config.js ├── tsconfig.json └── tsconfig.recipe.json ├── dev.env ├── docs ├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── Plan ├── README.md ├── babel.config.js ├── docs │ ├── deployment-guide.md │ ├── dhanyawad.md │ ├── getting-started.md │ ├── img │ │ └── feautres_blog.png │ ├── introduction.md │ ├── roadmap.md │ ├── understand-om-startup-framework │ │ ├── _category_.json │ │ ├── backend.md │ │ ├── blog.md │ │ ├── frontend │ │ │ ├── _category_.json │ │ │ ├── authentication.md │ │ │ ├── landing.md │ │ │ ├── pricing.md │ │ │ ├── privacy-policy-and-terms-conditions.md │ │ │ ├── tldr.md │ │ │ ├── url-shortner.md │ │ │ └── user-dashboard.md │ │ ├── github-actions.md │ │ └── kubernetes.md │ ├── what-is-om-startup-framework.md │ └── why-is-it-called-om-start-up-framework.md ├── docusaurus.config.js ├── package-lock.json ├── package.json ├── sidebars.js ├── src │ └── css │ │ └── custom.css ├── static │ ├── .nojekyll │ ├── ga.js │ └── img │ │ ├── ban_screen.png │ │ ├── ban_user.gif │ │ ├── delete_screen.png │ │ ├── delete_user.gif │ │ ├── deployment_replace_company.png │ │ ├── deployment_replace_domain.png │ │ ├── deployment_replace_email.png │ │ ├── deployment_replace_name.png │ │ ├── download_users.png │ │ ├── favicon.ico │ │ ├── feautres_action.png │ │ ├── feautres_blog.png │ │ ├── feautres_contact_us_modal.png │ │ ├── feautres_github_actions.png │ │ ├── feautres_landing.png │ │ ├── feautres_omkar_shortner.png │ │ ├── feautres_omkar_users.png │ │ ├── feautres_pricing.png │ │ ├── feautres_privacy.png │ │ ├── feautres_sign_up.png │ │ ├── getting_started_blog_starter.png │ │ ├── getting_started_server_starter.png │ │ ├── logo.svg │ │ ├── omkar.png │ │ ├── see_actions.gif │ │ └── twitter-card.png └── tsconfig.json ├── express-url-redirect ├── .dockerignore ├── .gitignore ├── Dockerfile ├── index.js ├── package-lock.json └── package.json ├── frontend ├── .dockerignore ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── Dockerfile ├── LICENSE ├── MantraSadhana ├── README.md ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── public │ ├── css │ │ ├── affiliate.css │ │ ├── landing.css │ │ └── pricing.css │ ├── images │ │ ├── 404_rainy_cloud_light.png │ │ ├── favicon │ │ │ ├── dev │ │ │ │ ├── favicon-16x16.png │ │ │ │ ├── favicon-256x256.png │ │ │ │ ├── favicon-32x32.png │ │ │ │ └── favicon-96x96.png │ │ │ └── prod │ │ │ │ ├── favicon-16x16.png │ │ │ │ ├── favicon-256x256-with-bg.png │ │ │ │ ├── favicon-256x256.png │ │ │ │ ├── favicon-32x32.png │ │ │ │ └── favicon-96x96.png │ │ ├── logo-eui.svg │ │ └── twitter-card.png │ ├── lp-img │ │ ├── adobe.png │ │ ├── checked-ico.svg │ │ ├── cup-ico.svg │ │ ├── ge.png │ │ ├── ico-1.svg │ │ ├── ico-6.svg │ │ ├── illustration-1.png │ │ ├── illustration-2.png │ │ ├── illustration-3.png │ │ ├── illustration-4.png │ │ ├── illustration-5.png │ │ ├── illustration.png │ │ ├── illustration.svg │ │ ├── oracle.png │ │ ├── paytm.png │ │ ├── pg.png │ │ ├── qb.png │ │ ├── rgc.png │ │ ├── signa.png │ │ └── sl.png │ └── robots.txt ├── sandbox.config.json ├── src │ ├── components │ │ ├── CenterContent.tsx │ │ ├── CenteredSpinner.tsx │ │ ├── DashboardContent.tsx │ │ ├── DashboardWrapper.tsx │ │ ├── Faq.tsx │ │ ├── Feedback.tsx │ │ ├── FilterPanel.tsx │ │ ├── Footer.tsx │ │ ├── H1Text.tsx │ │ ├── LandingContent.tsx │ │ ├── Seo.tsx │ │ ├── SimpleContactUsModal.tsx │ │ ├── SimpleSignUpModal.tsx │ │ ├── auth │ │ │ ├── context.tsx │ │ │ └── index.tsx │ │ ├── chrome │ │ │ └── index.tsx │ │ ├── forms │ │ │ └── ContactForm.tsx │ │ ├── inputs │ │ │ ├── CheckBoxField.tsx │ │ │ ├── MultiSelect.tsx │ │ │ ├── NumberField.tsx │ │ │ ├── SearchField.tsx │ │ │ ├── Select.tsx │ │ │ ├── SingleSelect.tsx │ │ │ ├── TextAreaField.tsx │ │ │ ├── TextField.tsx │ │ │ └── outputs.tsx │ │ ├── modals │ │ │ ├── useDemoModal.tsx │ │ │ ├── useDiscountContactUs.tsx │ │ │ ├── useGenericContactUs.tsx │ │ │ ├── useInquiryContactUs.tsx │ │ │ ├── usePricingContactUs.tsx │ │ │ └── useSimpleSignUpModal.tsx │ │ ├── starter │ │ │ ├── header.styles.ts │ │ │ ├── header.tsx │ │ │ └── wrapper.tsx │ │ └── theme.tsx │ ├── custom_typings │ │ └── index.d.ts │ ├── layouts │ │ ├── AuthedDashboard.tsx │ │ ├── AuthedLanding.tsx │ │ ├── ToolsDashboard.tsx │ │ ├── UnAuthedLanding.tsx │ │ └── YouTubeDashboard.tsx │ ├── lib │ │ ├── loader.ts │ │ └── theme.ts │ ├── pages │ │ ├── 404.tsx │ │ ├── _app.tsx │ │ ├── _document.tsx │ │ ├── _error.tsx │ │ ├── affiliates.tsx │ │ ├── auth │ │ │ ├── forgot-password.tsx │ │ │ ├── reset-password-verify │ │ │ │ └── [token].tsx │ │ │ ├── sign-in.tsx │ │ │ └── sign-up.tsx │ │ ├── get-started.tsx │ │ ├── index.tsx │ │ ├── legal │ │ │ ├── privacy.tsx │ │ │ └── terms.tsx │ │ ├── messages │ │ │ ├── banned.tsx │ │ │ ├── deleted.tsx │ │ │ ├── reset-password-expired-or-invalid.tsx │ │ │ ├── sign-up-email-verify-expired.tsx │ │ │ └── sign-up-email-verify-invalid.tsx │ │ ├── onboarding.tsx │ │ ├── pricing.tsx │ │ ├── referral-program.tsx │ │ ├── sitemap.xml.js │ │ └── staff │ │ │ ├── actions.tsx │ │ │ ├── pricing.tsx │ │ │ ├── short-urls.tsx │ │ │ ├── stats.tsx │ │ │ └── users │ │ │ ├── [id].tsx │ │ │ └── index.tsx │ ├── styles │ │ ├── getting-started.styles.ts │ │ └── global.styles.ts │ └── utils │ │ ├── analytics.ts │ │ ├── api.tsx │ │ ├── axios │ │ ├── apply-interceptors.ts │ │ ├── backend.ts │ │ ├── fetch-adapter.ts │ │ ├── frontend.ts │ │ ├── index.ts │ │ └── use-axios.ts │ │ ├── backend │ │ └── types.ts │ │ ├── config.ts │ │ ├── data │ │ ├── faq.tsx │ │ ├── links.tsx │ │ ├── options.tsx │ │ └── validators.ts │ │ ├── hocs │ │ └── withAsyncSearchHoc.tsx │ │ ├── hooks.ts │ │ ├── messages.tsx │ │ ├── missc.ts │ │ ├── next │ │ └── index.tsx │ │ └── types.ts └── tsconfig.json └── k8s ├── app ├── backend-depl.yaml ├── blog-depl.yaml ├── express-url-redirect-depl.yaml ├── frontend-depl.yaml └── ingress-srv.yaml ├── certificates └── certificates.yaml └── volumes └── pvc-1.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Contentlayer 2 | .contentlayer 3 | # app-data/db.sqlite3 4 | .env -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Chetan Jain 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app-data/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omkarcloud/om-startup-framework/6d4245c4d9d8bcb84ea7525245947fb0687176b7/app-data/db.sqlite3 -------------------------------------------------------------------------------- /backend/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.formatting.provider": "autopep8" 3 | } 4 | -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9 2 | ENV PYTHONBUFFERED 1 3 | 4 | RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - 5 | RUN apt-get install -y nodejs 6 | 7 | COPY requirements.txt . 8 | RUN python -m pip install -r requirements.txt 9 | 10 | RUN mkdir app 11 | WORKDIR /app 12 | COPY . /app 13 | 14 | CMD gunicorn --workers 3 -b 0.0.0.0:8000 config.wsgi -------------------------------------------------------------------------------- /backend/backend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omkarcloud/om-startup-framework/6d4245c4d9d8bcb84ea7525245947fb0687176b7/backend/backend/__init__.py -------------------------------------------------------------------------------- /backend/backend/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /backend/backend/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BackendConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'backend' 7 | -------------------------------------------------------------------------------- /backend/backend/custom_exception_handler.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | from backend.utils.exceptions import vague_message, CustomException, VagueException 3 | from backend.models import UserAction 4 | from backend.utils.model_utils import save_action 5 | from backend.models import User 6 | from backend.validators import normalize_django_errors 7 | from backend.validators import errors_dict_to_string 8 | from rest_framework.views import exception_handler 9 | from rest_framework import exceptions 10 | from rest_framework import status 11 | from rest_framework.response import Response 12 | 13 | def custom_exception_handler(exc, context): 14 | print(exc) 15 | print(traceback.format_exc()) 16 | response = exception_handler(exc, context) 17 | error_result = str(exc) + '\n' + traceback.format_exc() 18 | 19 | req = context['request'] 20 | save_action(req, UserAction.type_exception, {"exception": error_result}) 21 | 22 | 23 | if isinstance(exc, User.DoesNotExist): 24 | return Response({"message": 'You have been Deleted'}, status=status.HTTP_404_NOT_FOUND) 25 | 26 | if response is not None: 27 | if isinstance(exc, exceptions.NotAuthenticated): 28 | response.data = {'redirect': '/auth/sign-in/'} 29 | elif isinstance(exc, CustomException): 30 | response.data = {"message": exc.message} 31 | elif isinstance(exc, VagueException): 32 | response.data = {"message": vague_message} 33 | elif isinstance(exc, exceptions.ValidationError): 34 | response.data = {"message": errors_dict_to_string( 35 | normalize_django_errors(response.data))} 36 | else: 37 | if 'detail' in response.data: 38 | response.data['message'] = response.data['detail'] 39 | del response.data['detail'] 40 | return response 41 | -------------------------------------------------------------------------------- /backend/backend/management/commands/seed.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from backend.models import User 3 | from backend.utils.hash_password import get_hashed_password 4 | 5 | 6 | def create_user(name, email): 7 | user = User() 8 | user.name = name 9 | user.email = email 10 | user.password = get_hashed_password('12345678') 11 | user.auth_method = User.email_auth_method 12 | user.has_on_boarded = True 13 | user.save() 14 | return user.id 15 | 16 | def delete_all(): 17 | User.objects.all().delete() 18 | 19 | class Command(BaseCommand): 20 | help = 'seeds data for developement' 21 | 22 | def add_arguments(self, parser): 23 | pass 24 | 25 | def handle(self, *args, **options): 26 | # Uncomment the following line to delete Users from the database. 27 | # delete_all() 28 | 29 | create_user('Admin', 'admin@admin.com') 30 | create_user('Sanskar', 'sanskar@sanskar.com') 31 | create_user('Sariputra', 'sariputra@sariputra.com') 32 | 33 | self.stdout.write(self.style.SUCCESS('Done Seeding Database')) 34 | -------------------------------------------------------------------------------- /backend/backend/messages.py: -------------------------------------------------------------------------------- 1 | Messages = { 2 | 'AUTH_STRONGER_PASSWORD':'Please enter a stornger password. Try increasing the length of your password by using a multi-word phrase.' 3 | ,'UPLOAD_FAILED': 'Failed to upload some files.' 4 | } 5 | -------------------------------------------------------------------------------- /backend/backend/middlewares.py: -------------------------------------------------------------------------------- 1 | from backend.views.auth import set_auth_cookies 2 | from backend.utils.jwt import verify_access_token_and_get_id, verify_refresh_token_and_get_id 3 | from django.conf import settings 4 | 5 | 6 | def is_admin(user_id) -> bool: 7 | admin_ids = ["1"] 8 | return str(user_id) in admin_ids 9 | 10 | is_authenticated = True 11 | # is_authenticated = False 12 | 13 | def handle_request(request): 14 | should_set_auth_cookies = False 15 | access_token = request.COOKIES.get('access_token') 16 | id = verify_access_token_and_get_id(access_token) 17 | request.user_id = None 18 | if id is not None: 19 | request.user_id = id 20 | else: 21 | refresh_token = request.COOKIES.get('refresh_token') 22 | id = verify_refresh_token_and_get_id(refresh_token) 23 | if id is not None: 24 | request.user_id = id 25 | should_set_auth_cookies = True 26 | 27 | # For Development Only 28 | if settings.IN_LOCAL: 29 | if is_authenticated: 30 | request.user_id = 1 31 | 32 | request.is_admin = request.user_id is not None and is_admin(request.user_id) 33 | 34 | return id, should_set_auth_cookies 35 | 36 | 37 | def auth_middleware(get_response): 38 | 39 | def middleware(request): 40 | id, should_set_auth_cookies = handle_request(request) 41 | response = get_response(request) 42 | if should_set_auth_cookies: 43 | set_auth_cookies(response, id) 44 | 45 | return response 46 | 47 | return middleware 48 | -------------------------------------------------------------------------------- /backend/backend/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omkarcloud/om-startup-framework/6d4245c4d9d8bcb84ea7525245947fb0687176b7/backend/backend/migrations/__init__.py -------------------------------------------------------------------------------- /backend/backend/pagination.py: -------------------------------------------------------------------------------- 1 | import math 2 | from urllib import parse 3 | from rest_framework.pagination import PageNumberPagination 4 | from rest_framework.response import Response 5 | 6 | 7 | def remove_localhost_prefix(link): 8 | if link is None: 9 | return None 10 | result = parse.urlsplit(link) 11 | return result.path + '?' + result.query 12 | 13 | 14 | class DefaultPageNumberPagination(PageNumberPagination): 15 | page_size = 10000 16 | 17 | def get_paginated_response(self, data): 18 | total_pages = math.ceil(self.page.paginator.count / self.page_size) 19 | result = { 20 | 'next': self.get_next_link(), 21 | 'previous': self.get_previous_link(), 22 | 'count': self.page.paginator.count, 23 | "total_pages": 1 if total_pages == 0 else total_pages , 24 | 'results': data, 25 | } 26 | return Response(result) 27 | 28 | def get_previous_link(self): 29 | link = super(DefaultPageNumberPagination, self).get_previous_link() 30 | return remove_localhost_prefix(link) 31 | 32 | def get_next_link(self): 33 | link = super(DefaultPageNumberPagination, self).get_next_link() 34 | return remove_localhost_prefix(link) 35 | -------------------------------------------------------------------------------- /backend/backend/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework import permissions 2 | 3 | class IsAuthenticated(permissions.BasePermission): 4 | def has_permission(self, request, view): 5 | return request.user_id is not None 6 | 7 | class IsAdmin(permissions.BasePermission): 8 | def has_permission(self, request, view): 9 | return request.is_admin -------------------------------------------------------------------------------- /backend/backend/shortcuts.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from rest_framework.response import Response 3 | from rest_framework import status 4 | from django.conf import settings 5 | 6 | 7 | def bad_request(data): 8 | return Response(data, status=status.HTTP_400_BAD_REQUEST) 9 | 10 | 11 | def client_side_redirect(url): 12 | # this will be intercepted by axios and user will navigate to the url 13 | return Response({'redirect': url}) 14 | 15 | 16 | def get_domain(set_on_sub_domain): 17 | if set_on_sub_domain: 18 | return f".{settings.DOMAIN_NAME}" 19 | return None 20 | 21 | 22 | def set_cookie(response, key, value, set_on_sub_domain): 23 | expires = datetime.datetime.utcnow() + datetime.timedelta(days=365) 24 | domain = get_domain(set_on_sub_domain) 25 | 26 | # For Testing makin it un secure on local 27 | secure = False if settings.IN_LOCAL else True 28 | response.set_cookie( 29 | key, 30 | value, secure=secure, httponly=True, samesite='Lax', expires=expires) 31 | 32 | 33 | def delete_cookie(response, key, set_on_sub_domain): 34 | domain = get_domain(set_on_sub_domain) 35 | response.delete_cookie(key) 36 | -------------------------------------------------------------------------------- /backend/backend/templates/mail_buy_interest.html: -------------------------------------------------------------------------------- 1 |

Hi {{fname}},

2 |

Thank you for reaching out to the yourcompanyname team. I have received your Buying Interest Inquiry and I am happy to show how yourcompanyname could help you grow your business.

3 | 4 |

Let me know your questions, or if you prefer, you can schedule a meeting using https://www.yourdomain.com/l/meet-chetan/ to explore tailored strategies to help you grow your Customer Base.

5 | 6 |

Best regards,
7 | Chetan Jain

-------------------------------------------------------------------------------- /backend/backend/templates/mail_contact_us.html: -------------------------------------------------------------------------------- 1 |
2 |   Name: {{name}}
3 |   Phone: {{phone}}
4 |   Message: {{message}}
5 | 
-------------------------------------------------------------------------------- /backend/backend/templates/mail_demo_interest.html: -------------------------------------------------------------------------------- 1 |

Hi {{fname}},

2 | 3 |

Thank you for your interest in Product Demo of yourcompanyname. I would be more than happy to provide you with a product demo and discuss how yourcompanyname could specifically help grow your business.

4 | 5 |

To book a meeting with me, please visit https://www.yourdomain.com/l/meet-chetan/ and select a time that works best for you. I will be happy to walk you through the software and answer any questions you may have.

6 | 7 |

I look forward to speaking with you and showing you how yourcompanyname can help grow your Customer Base.

8 | 9 |

Best regards,
10 | Chetan Jain

-------------------------------------------------------------------------------- /backend/backend/templates/mail_discount_interest.html: -------------------------------------------------------------------------------- 1 |

Hi {{fname}},

2 | 3 |

Thank you for reaching out to the yourcompanyname team. I have received your discount inquiry interest and I am happy to discuss your requirements further. Could you kindly provide me with your availability, so that we can schedule a call at your convenience?

4 | 5 |

I am looking forward to speaking with you! If you have 10 minutes to spare, do let me know. Alternatively, you can press the link below to select a time on my calendar that works best for you.

6 | 7 |

https://www.yourdomain.com/l/meet-chetan/

8 | 9 |

Best regards,
10 | Chetan Jain

11 | -------------------------------------------------------------------------------- /backend/backend/templates/mail_generic.html: -------------------------------------------------------------------------------- 1 |
2 |     {{data}}
3 | 
4 | -------------------------------------------------------------------------------- /backend/backend/templates/mail_inquiry_interest.html: -------------------------------------------------------------------------------- 1 |

Hi {{fname}},

2 | 3 |

Thank you for reaching out to the yourcompanyname team. I have received your inquiry and I am happy to discuss your requirements further. Could you kindly provide me with your availability, so that we can schedule a call at your convenience?

4 | 5 |

I am looking forward to speaking with you! If you have 10 minutes to spare, do let me know. Alternatively, you can press the link below to select a time on my calendar that works best for you.

6 | 7 |

https://www.yourdomain.com/l/meet-chetan/

8 | 9 |

Best regards,
10 | Chetan Jain

-------------------------------------------------------------------------------- /backend/backend/templates/mail_meeting.html: -------------------------------------------------------------------------------- 1 |

2 | Hi {{fname}},
3 | This is Chetan from yourcompanyname. I am reaching out to you as you have shown interest in using our Lead Generation Software to find potential customers.

4 | 5 | I'd like to arrange a meeting with you to discuss specific strategies that can help you increase your customer base.

6 | 7 | Do visit https://www.yourdomain.com/l/meet-chetan/ to schedule a meeting with me.

11 | 12 | Regards,
13 | Chetan Jain
14 |

-------------------------------------------------------------------------------- /backend/backend/templates/mail_suggested_feautre.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |       Content: {{content}}
4 |    
5 |
-------------------------------------------------------------------------------- /backend/backend/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omkarcloud/om-startup-framework/6d4245c4d9d8bcb84ea7525245947fb0687176b7/backend/backend/tests/__init__.py -------------------------------------------------------------------------------- /backend/backend/tests/test_api.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from backend.models import User, SignUpToken, ResetPasswordToken 3 | from backend.messages import Messages 4 | 5 | class ApiTestCase(TestCase): 6 | valid_user = { 7 | "name": "a", 8 | "email": "a@gmail.com", 9 | "password": "qwer14586s", 10 | } 11 | 12 | def create_user(self): 13 | sign_up_response = self.client.post( 14 | "/backend/auth/sign-up/", self.valid_user) 15 | token = SignUpToken.objects.get(pk=1) 16 | response = self.client.get( 17 | f'/backend/auth/sign-up-email-verify/{token.token}/') 18 | return response 19 | 20 | def test_sign_up(self): 21 | response = self.create_user() 22 | self.assertEqual(response.url, '/') 23 | user = User.objects.get(pk=1) 24 | self.assertEqual(user.name, self.valid_user["name"]) 25 | -------------------------------------------------------------------------------- /backend/backend/tests/test_email.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from backend.validators import is_company_email 4 | 5 | class ApiTestCase(TestCase): 6 | def test_temporary_email(self): 7 | self.assertFalse(is_company_email('john.doe@mailinator.com')) 8 | 9 | def test_gmail(self): 10 | self.assertFalse(is_company_email('john.doe@gmail.com')) 11 | 12 | def test_free_domain(self): 13 | self.assertFalse(is_company_email('john.doe@freespam.com')) 14 | 15 | def test_free_domain_2(self): 16 | self.assertFalse(is_company_email('john.doe@xyz.tk')) 17 | 18 | def test_free_domain_3(self): 19 | self.assertFalse(is_company_email('john.doe@xyz.cu.cc')) 20 | 21 | def test_company_email(self): 22 | self.assertTrue(is_company_email('john.doe@google.com')) 23 | -------------------------------------------------------------------------------- /backend/backend/utils/data.py: -------------------------------------------------------------------------------- 1 | ONLY_POST_METHOD = ['post', 'head', 'options'] 2 | -------------------------------------------------------------------------------- /backend/backend/utils/email_templates.py: -------------------------------------------------------------------------------- 1 | import json 2 | from backend.utils.send_email import send_email 3 | from django.conf import settings 4 | from django.conf import settings 5 | 6 | def create_reset_password_link(token): 7 | return f'{settings.APP_URL}/auth/reset-password-verify/{token}/' 8 | 9 | 10 | def send_reset_password_verify_email(token, to_email): 11 | link = create_reset_password_link(token) 12 | send_email('Reset Password Verification Email', 13 | 'mail_reset_password_verify.html', {'link': link}, to_email) 14 | 15 | def send_generic_email(subject, content): 16 | text_content = json.dumps(content, indent=4) 17 | content = f"{subject}\n{text_content}" 18 | 19 | send_email(subject, 20 | 'mail_generic.html', {"data": text_content }, settings.MAIL_RECIEVER_EMAIL) 21 | 22 | def send_meeting_email(fname, to_email): 23 | send_email("Schedule Meeting", 24 | 'mail_meeting.html', {"fname": fname }, to_email, settings.SALES_EMAIL) 25 | 26 | def send_user_email(fname, to_email, subject, template): 27 | send_email(subject, 28 | template, {"fname": fname }, to_email, settings.SALES_EMAIL) 29 | 30 | 31 | def create_sign_up_verify_link(token): 32 | return f'{settings.APP_URL}/backend/auth/sign-up-email-verify/{token}/' 33 | 34 | def send_sign_up_verify_email(token, to_email): 35 | link = create_sign_up_verify_link(token) 36 | send_email('Sign Up Verification Email', 37 | 'mail_sign_up_verify.html', {'link': link}, to_email) -------------------------------------------------------------------------------- /backend/backend/utils/exceptions.py: -------------------------------------------------------------------------------- 1 | from rest_framework import exceptions 2 | from rest_framework import status 3 | 4 | vague_message = 'Something went wrong, please try again later.' 5 | 6 | 7 | class VagueException(exceptions.ValidationError): 8 | default_detail = (vague_message) 9 | 10 | 11 | class CustomException(exceptions.ValidationError): 12 | message = None 13 | 14 | def __init__(self, message=vague_message, status_code=status.HTTP_400_BAD_REQUEST): 15 | self.message = message 16 | super().__init__(None, None) 17 | self.status_code = status_code 18 | 19 | -------------------------------------------------------------------------------- /backend/backend/utils/hash_password.py: -------------------------------------------------------------------------------- 1 | import bcrypt 2 | 3 | def get_hashed_password(plain_text_password): 4 | return bcrypt.hashpw(plain_text_password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') 5 | 6 | 7 | def check_password(plain_text_password, hashed_password): 8 | # Check hashed password. Using bcrypt, the salt is saved into the hash itself 9 | return bcrypt.checkpw(plain_text_password.encode('utf-8'), hashed_password.encode('utf-8')) 10 | -------------------------------------------------------------------------------- /backend/backend/utils/ipinfo.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | from backend.utils.exceptions import VagueException 4 | 5 | 6 | def get_client_ip(request): 7 | x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') 8 | if x_forwarded_for: 9 | ip = x_forwarded_for.split(',')[0] 10 | else: 11 | ip = request.META.get('REMOTE_ADDR') 12 | return ip 13 | 14 | 15 | def get_ipinfo_data(ip): 16 | response = requests.get( 17 | f"https://www.omkar.cloud/backend/ipinfo/?ip={ip}" 18 | ) 19 | 20 | if not response.ok: 21 | raise VagueException() 22 | 23 | data = response.json() 24 | return data -------------------------------------------------------------------------------- /backend/backend/utils/jwt.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | import datetime 3 | import jwt 4 | 5 | 6 | def create_access_token(id): 7 | return jwt.encode({"id": id, "exp": datetime.datetime.utcnow() + datetime.timedelta(days=1)}, settings.SECRET_ACCESS_TOKEN, algorithm="HS256") 8 | 9 | 10 | def create_refresh_token(id): 11 | return jwt.encode({"id": id, "exp": datetime.datetime.utcnow() + datetime.timedelta(days=14)}, 12 | settings.SECRET_REFRESH_TOKEN, algorithm="HS256") 13 | 14 | def verify_token_and_get_id(token, secret): 15 | if token is None: 16 | return None 17 | try: 18 | payload = jwt.decode( 19 | token, secret, algorithms=["HS256"]) 20 | return payload["id"] 21 | except: 22 | return None 23 | 24 | 25 | def verify_access_token_and_get_id(token): 26 | return verify_token_and_get_id(token, settings.SECRET_ACCESS_TOKEN) 27 | 28 | 29 | def verify_refresh_token_and_get_id(token): 30 | return verify_token_and_get_id(token, settings.SECRET_REFRESH_TOKEN) 31 | -------------------------------------------------------------------------------- /backend/backend/utils/model_utils.py: -------------------------------------------------------------------------------- 1 | from django.db.utils import OperationalError 2 | 3 | from backend.utils.ipinfo import get_client_ip 4 | from backend.models import UserAction 5 | from backend.utils.exceptions import CustomException 6 | from backend.models import User 7 | 8 | 9 | def get_user(id): 10 | try: 11 | u = User.objects.get(id=id) 12 | return u 13 | except User.DoesNotExist: 14 | raise CustomException("User does not exist") 15 | 16 | def safe_save(user_action): 17 | # Weak Database Fix 18 | try: 19 | user_action.save() 20 | except OperationalError as e: 21 | print("Failed") 22 | pass 23 | 24 | def get_current_user(request): 25 | return get_user(request.user_id) 26 | 27 | 28 | 29 | 30 | def save_action(request, type, data, user = None): 31 | def get_doer(): 32 | if user is not None: 33 | return user 34 | if request.user_id is not None: 35 | return get_current_user(request) 36 | return None 37 | 38 | user_agent = request.META.get('HTTP_USER_AGENT') 39 | 40 | 41 | action = type 42 | data = data 43 | doer = get_doer() 44 | 45 | user_action = UserAction(user=doer, user_agent=user_agent, data=data, type=action, ip=get_client_ip(request)) 46 | safe_save(user_action) -------------------------------------------------------------------------------- /backend/backend/utils/send_email.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.template.loader import render_to_string 3 | from django.utils.html import strip_tags 4 | import requests 5 | from backend.utils.exceptions import VagueException 6 | 7 | def send_email(subject, template_file, context, recipient_list, from_email = settings.MAIL_SENDER_EMAIL): 8 | html_content = render_to_string( 9 | template_file, context) 10 | 11 | text_content = strip_tags(html_content) 12 | headers = { 13 | 'accept': 'application/json', 14 | 'api-key': settings.BREVO_API_KEY, 15 | 'content-type': 'application/json', 16 | } 17 | 18 | json_data = { 19 | 'sender': { 20 | 'name': 'yourcompanyname', 21 | 'email': from_email, 22 | }, 23 | 'to': [ 24 | { 25 | 'email': recipient_list, 26 | }, 27 | ], 28 | 'subject': subject, 29 | 'htmlContent': html_content, 30 | } 31 | 32 | response = requests.post('https://api.sendinblue.com/v3/smtp/email', headers=headers, json=json_data) 33 | 34 | if not response.ok: 35 | raise VagueException() 36 | 37 | return 38 | -------------------------------------------------------------------------------- /backend/backend/utils/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import hashlib 3 | import uuid 4 | from django.http import HttpResponse 5 | import csv 6 | import io 7 | 8 | def generate_uuid_utf8(): 9 | return str(uuid.uuid4()) 10 | 11 | def generate_uuid(): 12 | return uuid.uuid4().hex 13 | 14 | def hash_string(str): 15 | return hashlib.md5(bytes(str, 'utf8')).hexdigest() 16 | 17 | def download_csv_response(result, fieldnames): 18 | 19 | # Create a buffer for the CSV 20 | buffer = io.StringIO() 21 | 22 | # Write the data to the buffer 23 | writer = csv.DictWriter(buffer, fieldnames=fieldnames) 24 | writer.writeheader() 25 | writer.writerows(result) 26 | 27 | # Rewind the buffer 28 | buffer.seek(0) 29 | response = HttpResponse(buffer, content_type='text/csv') 30 | response['Content-Disposition'] = 'attachment; filename=leads.csv' 31 | 32 | return response 33 | 34 | def download_json(data): 35 | json_str = json.dumps(data, indent=4) 36 | 37 | # Create an HttpResponse object with the JSON content type 38 | response = HttpResponse(json_str, content_type='application/json') 39 | 40 | # Set the content-disposition header to prompt a download 41 | response['Content-Disposition'] = 'attachment; filename="data.json"' 42 | 43 | return response 44 | -------------------------------------------------------------------------------- /backend/backend/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omkarcloud/om-startup-framework/6d4245c4d9d8bcb84ea7525245947fb0687176b7/backend/backend/views/__init__.py -------------------------------------------------------------------------------- /backend/backend/views/ipinfo.py: -------------------------------------------------------------------------------- 1 | from rest_framework.decorators import api_view 2 | from rest_framework.response import Response 3 | from backend.utils.ipinfo import get_client_ip, get_ipinfo_data 4 | 5 | @api_view(['GET']) 6 | def get_ipinfo(request): 7 | ip = request.query_params.get("ip") if request.query_params.get("ip") is not None else get_client_ip(request) 8 | 9 | ip_data = get_ipinfo_data(ip) 10 | return Response(ip_data) -------------------------------------------------------------------------------- /backend/backend/views/send_email.py: -------------------------------------------------------------------------------- 1 | from backend.views.auth import get_first_name 2 | from backend.models import * 3 | from backend.serializers import * 4 | from rest_framework.viewsets import ModelViewSet 5 | from backend.utils.data import ONLY_POST_METHOD 6 | from backend.utils.email_templates import * 7 | 8 | demo_interest_subject = "Demo Interest" 9 | discount_interest_subject = "Discount Interest" 10 | inquiry_interest_subject = "Inquiry Interest" 11 | buy_interest_subject = "Buy Interest" 12 | contactus_subject = "Contact Us" 13 | linkedin_interest_email = 'Get Phone by LinkedIn Profile' 14 | 15 | class SendEmailEventViewSet(ModelViewSet): 16 | http_method_names = ONLY_POST_METHOD 17 | serializer_class = SendEmailEventSerializer 18 | queryset = SendEmailEvent.objects.all() 19 | 20 | def perform_create(self, serializer): 21 | subject = serializer.validated_data['subject'] 22 | content = serializer.validated_data['content'] 23 | 24 | send_generic_email(subject, content) 25 | serializer.save() 26 | 27 | name = content['name'] 28 | fname = get_first_name(name) 29 | email = content['email'] 30 | 31 | if subject == demo_interest_subject: 32 | send_user_email(fname, email , "Thank you for your Demo Interest" , "mail_demo_interest.html") 33 | elif subject == discount_interest_subject: 34 | send_user_email(fname, email , "Thank you for your Discount Interest" , "mail_discount_interest.html") 35 | elif subject == inquiry_interest_subject: 36 | send_user_email(fname, email , "Thank you for your Inquiry" , "mail_inquiry_interest.html") 37 | elif subject == buy_interest_subject: 38 | send_user_email(fname, email , "Thank you for your Buying Interest" , "mail_buy_interest.html") 39 | elif subject == contactus_subject: 40 | send_user_email(fname, email , "Thank you for Contacting Us" , "mail_contact_us.html") 41 | -------------------------------------------------------------------------------- /backend/backend/views/stats.py: -------------------------------------------------------------------------------- 1 | from backend.models import UserAction, User 2 | from backend.permissions import IsAdmin, IsAuthenticated 3 | from rest_framework.response import Response 4 | from rest_framework.decorators import api_view, permission_classes 5 | import datetime 6 | 7 | @api_view(['POST']) 8 | @permission_classes([IsAuthenticated, IsAdmin]) 9 | def get_stats(request): 10 | 11 | date_param = request.data.get('date', str(datetime.date.today())) 12 | 13 | # Convert the date parameter to a date object 14 | try: 15 | date_filter = datetime.date.fromisoformat(date_param) 16 | except ValueError: 17 | return Response({'error': 'Invalid date format. Use YYYY-MM-DD.'}, status=400) 18 | 19 | # Filter users by their registration date which is equal to the date filter 20 | new_users = User.objects.filter(created_at__lte=date_filter + datetime.timedelta(days=1), created_at__gte=date_filter) 21 | 22 | # Filter user actions by their timestamp which is equal to the date filter 23 | user_actions = UserAction.objects.filter(created_at__lte=date_filter + datetime.timedelta(days=1), created_at__gte=date_filter) 24 | 25 | # Count distinct users from the filtered user actions 26 | active_today = user_actions.values('user').distinct().count() 27 | 28 | # Prepare the response data 29 | data = { 30 | 'new_users': new_users.count(), 31 | 'active_today_users': active_today 32 | } 33 | 34 | return Response(data) 35 | -------------------------------------------------------------------------------- /backend/backend/views/test.py: -------------------------------------------------------------------------------- 1 | from rest_framework.decorators import api_view 2 | from rest_framework.response import Response 3 | @api_view(['GET']) 4 | def test(request): 5 | return Response({"ok": True}) 6 | -------------------------------------------------------------------------------- /backend/backend/views/url_shortener.py: -------------------------------------------------------------------------------- 1 | from backend.utils.model_utils import save_action 2 | from backend.models import * 3 | from backend.pagination import DefaultPageNumberPagination 4 | from backend.permissions import IsAdmin, IsAuthenticated 5 | from backend.serializers import * 6 | from rest_framework.viewsets import ModelViewSet 7 | from backend.utils.email_templates import * 8 | from rest_framework import filters 9 | from rest_framework.response import Response 10 | from rest_framework.decorators import api_view 11 | from django.shortcuts import redirect 12 | 13 | class ShortURLViewSet(ModelViewSet): 14 | queryset = ShortURL.objects.all() 15 | serializer_class = ShortURLSerializer 16 | permission_classes = [IsAuthenticated, IsAdmin] 17 | pagination_class = DefaultPageNumberPagination 18 | filter_backends = [filters.SearchFilter] 19 | search_fields = ['original_url', 'short_url'] 20 | 21 | @api_view(['GET']) 22 | def redirect_short_url(request, code): 23 | try: 24 | short_url = ShortURL.objects.get(short_url=code) 25 | short_url.click_count += 1 26 | short_url.save() 27 | 28 | save_action(request, UserAction.type_short_url_click, {"short_url": code, "original_url": short_url.original_url}) 29 | 30 | return redirect(short_url.original_url) 31 | except ShortURL.DoesNotExist: 32 | return Response({'message': 'Short URL does not exist.'}, status=404) -------------------------------------------------------------------------------- /backend/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omkarcloud/om-startup-framework/6d4245c4d9d8bcb84ea7525245947fb0687176b7/backend/config/__init__.py -------------------------------------------------------------------------------- /backend/config/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for config project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /backend/config/urls.py: -------------------------------------------------------------------------------- 1 | """config URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/4.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | 19 | from backend.views import url_shortener 20 | from .views import downloads 21 | 22 | urlpatterns = [ 23 | # path('admin/', admin.site.urls), 24 | path('backend/', include('backend.urls')), 25 | path('l//', url_shortener.redirect_short_url, name='redirect_short_url'), 26 | path('downloads/', downloads.download_file, name='download_file'), 27 | path('__debug__/', include('debug_toolbar.urls')), 28 | ] 29 | -------------------------------------------------------------------------------- /backend/config/views/downloads.py: -------------------------------------------------------------------------------- 1 | import os 2 | from django.http import HttpResponse, Http404 3 | 4 | from backend.models import * 5 | from backend.utils.model_utils import save_action 6 | 7 | 8 | def relative_path(path, goback=0): 9 | levels = [".."] * (goback + -1) 10 | return os.path.abspath(os.path.join(os.getcwd(), *levels, path)) 11 | 12 | 13 | def download_file(request, filename): 14 | file_path = os.path.join(relative_path('downloads'), filename) 15 | has_file = os.path.exists(file_path) 16 | if has_file: 17 | with open(file_path, 'rb') as fh: 18 | response = HttpResponse(fh.read(), content_type="application/octet-stream") 19 | response['Content-Disposition'] = 'attachment; filename=' + os.path.basename(file_path) 20 | 21 | save_action(request, UserAction.file_download, {"filename": filename}) 22 | 23 | return response 24 | raise Http404 -------------------------------------------------------------------------------- /backend/config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for config project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /backend/downloads/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omkarcloud/om-startup-framework/6d4245c4d9d8bcb84ea7525245947fb0687176b7/backend/downloads/banner.png -------------------------------------------------------------------------------- /backend/downloads/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omkarcloud/om-startup-framework/6d4245c4d9d8bcb84ea7525245947fb0687176b7/backend/downloads/logo.png -------------------------------------------------------------------------------- /backend/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.11", 3 | "scripts": { 4 | "dev": "python3 manage.py runserver 0.0.0.0:8000", 5 | "test": "python3 manage.py test", 6 | "shell": "python3 manage.py shell", 7 | "db:makemigrations": "python3 manage.py makemigrations && python3 manage.py makemigrations backend", 8 | "db:migrate": "python3 manage.py migrate && python3 manage.py migrate backend", 9 | "db:delete-and-seed": "rm -rf ../app-data/db.sqlite3 && npm run db:migrate && python3 manage.py seed", 10 | "db:delete-makemigrations-and-seed": "rm -rf ../app-data/db.sqlite3 && npm run db:makemigrations && npm run db:migrate && python3 manage.py seed" 11 | } 12 | } -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | 2 | bcrypt==3.2.0 3 | Django==4.0.3 4 | django-debug-toolbar==3.2.4 5 | djangorestframework==3.13.1 6 | PyJWT==2.3.0 7 | requests==2.27.1 8 | gunicorn==19.10.0 9 | python-dotenv==0.20.0 10 | django-cors-headers==3.5.0 11 | pydash==6.0.0 12 | django-filter==22.1 -------------------------------------------------------------------------------- /blog/.env.example: -------------------------------------------------------------------------------- 1 | # visit https://giscus.app to get your Giscus ids 2 | NEXT_PUBLIC_GISCUS_REPO= 3 | NEXT_PUBLIC_GISCUS_REPOSITORY_ID= 4 | NEXT_PUBLIC_GISCUS_CATEGORY= 5 | NEXT_PUBLIC_GISCUS_CATEGORY_ID= 6 | NEXT_PUBLIC_UTTERANCES_REPO= 7 | NEXT_PUBLIC_DISQUS_SHORTNAME= 8 | 9 | 10 | MAILCHIMP_API_KEY= 11 | MAILCHIMP_API_SERVER= 12 | MAILCHIMP_AUDIENCE_ID= 13 | 14 | BUTTONDOWN_API_KEY= 15 | 16 | CONVERTKIT_API_KEY= 17 | # curl https://api.convertkit.com/v3/forms?api_key= to get your form ID 18 | CONVERTKIT_FORM_ID= 19 | 20 | KLAVIYO_API_KEY= 21 | KLAVIYO_LIST_ID= 22 | 23 | REVUE_API_KEY= 24 | 25 | EMAILOCTOPUS_API_KEY= 26 | EMAILOCTOPUS_LIST_ID= 27 | -------------------------------------------------------------------------------- /blog/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /blog/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | env: { 5 | browser: true, 6 | amd: true, 7 | node: true, 8 | es6: true, 9 | }, 10 | plugins: ['@typescript-eslint'], 11 | extends: [ 12 | 'eslint:recommended', 13 | 'plugin:@typescript-eslint/eslint-recommended', 14 | 'plugin:@typescript-eslint/recommended', 15 | 'plugin:jsx-a11y/recommended', 16 | 'plugin:prettier/recommended', 17 | 'next', 18 | 'next/core-web-vitals', 19 | ], 20 | rules: { 21 | "@next/next/no-html-link-for-pages": "off", 22 | 'prettier/prettier': 'warn', 23 | 'react/react-in-jsx-scope': 'off', 24 | 'jsx-a11y/anchor-is-valid': [ 25 | 'error', 26 | { 27 | components: ['Link'], 28 | specialLink: ['hrefLeft', 'hrefRight'], 29 | aspects: ['invalidHref', 'preferButton'], 30 | }, 31 | ], 32 | 'react/prop-types': 0, 33 | 'no-unused-vars': 0, 34 | 'react/no-unescaped-entities': 0, 35 | '@typescript-eslint/explicit-module-boundary-types': 'off', 36 | '@typescript-eslint/no-var-requires': 'off', 37 | '@typescript-eslint/ban-ts-comment': 'off', 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /blog/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | /.yarn/* 8 | !/.yarn/releases 9 | !/.yarn/plugins 10 | !/.yarn/sdks 11 | 12 | # testing 13 | /coverage 14 | 15 | # next.js 16 | /.next/ 17 | /out/ 18 | public/sitemap.xml 19 | .vercel 20 | 21 | # production 22 | /build 23 | *.xml 24 | 25 | # rss feed 26 | /public/feed.xml 27 | 28 | # search 29 | /public/search.json 30 | 31 | # misc 32 | .DS_Store 33 | 34 | # debug 35 | *.log 36 | npm-debug.log* 37 | yarn-debug.log* 38 | yarn-error.log* 39 | 40 | # local env files 41 | .env.local 42 | .env.development.local 43 | .env.test.local 44 | .env.production.local 45 | 46 | # Contentlayer 47 | .contentlayer 48 | -------------------------------------------------------------------------------- /blog/.husky/_/husky.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ -z "$husky_skip_init" ]; then 3 | debug () { 4 | [ "$HUSKY_DEBUG" = "1" ] && echo "husky (debug) - $1" 5 | } 6 | 7 | readonly hook_name="$(basename "$0")" 8 | debug "starting $hook_name..." 9 | 10 | if [ "$HUSKY" = "0" ]; then 11 | debug "HUSKY env variable is set to 0, skipping hook" 12 | exit 0 13 | fi 14 | 15 | if [ -f ~/.huskyrc ]; then 16 | debug "sourcing ~/.huskyrc" 17 | . ~/.huskyrc 18 | fi 19 | 20 | export readonly husky_skip_init=1 21 | sh -e "$0" "$@" 22 | exitCode="$?" 23 | 24 | if [ $exitCode != 0 ]; then 25 | echo "husky - $hook_name hook exited with code $exitCode (error)" 26 | exit $exitCode 27 | fi 28 | 29 | exit 0 30 | fi 31 | -------------------------------------------------------------------------------- /blog/.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install lint-staged 5 | -------------------------------------------------------------------------------- /blog/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine 2 | 3 | COPY package.json . 4 | RUN npm run clean 5 | COPY . . 6 | 7 | RUN npm run build 8 | 9 | CMD ["npm", "run", "start"] -------------------------------------------------------------------------------- /blog/components/Card.tsx: -------------------------------------------------------------------------------- 1 | import Image from './Image' 2 | import Link from './Link' 3 | 4 | const Card = ({ title, description, imgSrc, href }) => ( 5 |
6 |
11 | {imgSrc && 12 | (href ? ( 13 | 14 | {title} 21 | 22 | ) : ( 23 | {title} 30 | ))} 31 |
32 |

33 | {href ? ( 34 | 35 | {title} 36 | 37 | ) : ( 38 | title 39 | )} 40 |

41 |

{description}

42 | {href && ( 43 | 48 | Learn more → 49 | 50 | )} 51 |
52 |
53 |
54 | ) 55 | 56 | export default Card 57 | -------------------------------------------------------------------------------- /blog/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import siteMetadata from '@/data/siteMetadata' 2 | import headerNavLinks from '@/data/headerNavLinks' 3 | import Logo from '@/data/logo.svg' 4 | import Link from './Link' 5 | import MobileNav from './MobileNav' 6 | import ThemeSwitch from './ThemeSwitch' 7 | 8 | const Header = () => { 9 | return ( 10 |
11 |
12 | 13 |
14 |
15 | 16 |
17 | {typeof siteMetadata.headerTitle === 'string' ? ( 18 |
19 | {siteMetadata.headerTitle} 20 |
21 | ) : ( 22 | siteMetadata.headerTitle 23 | )} 24 |
25 |
26 |
27 |
28 |
29 | {headerNavLinks.map((link) => ( 30 | 35 | {link.title} 36 | 37 | ))} 38 |
39 | 40 | 41 |
42 |
43 | ) 44 | } 45 | 46 | export default Header 47 | -------------------------------------------------------------------------------- /blog/components/Image.tsx: -------------------------------------------------------------------------------- 1 | import NextImage, { ImageProps } from 'next/image' 2 | 3 | const Image = ({ ...rest }: ImageProps) => 4 | 5 | export default Image 6 | -------------------------------------------------------------------------------- /blog/components/LayoutWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { Inter } from '@next/font/google' 2 | import SectionContainer from './SectionContainer' 3 | import Footer from './Footer' 4 | import { ReactNode } from 'react' 5 | import Header from './Header' 6 | 7 | interface Props { 8 | children: ReactNode 9 | } 10 | 11 | const inter = Inter({ 12 | subsets: ['latin'], 13 | }) 14 | 15 | const LayoutWrapper = ({ children }: Props) => { 16 | return ( 17 | 18 |
19 |
20 |
{children}
21 |
22 |
23 |
24 | ) 25 | } 26 | 27 | export default LayoutWrapper 28 | -------------------------------------------------------------------------------- /blog/components/Link.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/anchor-has-content */ 2 | import Link from 'next/link' 3 | import { AnchorHTMLAttributes, DetailedHTMLProps } from 'react' 4 | 5 | const CustomLink = ({ 6 | href, 7 | ...rest 8 | }: DetailedHTMLProps, HTMLAnchorElement>) => { 9 | const isInternalLink = href && href.startsWith('/') 10 | const isAnchorLink = href && href.startsWith('#') 11 | 12 | if (isInternalLink) { 13 | // @ts-ignore 14 | return 15 | } 16 | 17 | if (isAnchorLink) { 18 | return 19 | } 20 | 21 | return 22 | } 23 | 24 | export default CustomLink 25 | -------------------------------------------------------------------------------- /blog/components/PageTitle.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | 3 | interface Props { 4 | children: ReactNode 5 | } 6 | 7 | export default function PageTitle({ children }: Props) { 8 | return ( 9 |

10 | {children} 11 |

12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /blog/components/ScrollTopAndComment.tsx: -------------------------------------------------------------------------------- 1 | import siteMetadata from '@/data/siteMetadata' 2 | import { useEffect, useState } from 'react' 3 | 4 | const ScrollTopAndComment = () => { 5 | const [show, setShow] = useState(false) 6 | 7 | useEffect(() => { 8 | const handleWindowScroll = () => { 9 | if (window.scrollY > 50) setShow(true) 10 | else setShow(false) 11 | } 12 | 13 | window.addEventListener('scroll', handleWindowScroll) 14 | return () => window.removeEventListener('scroll', handleWindowScroll) 15 | }, []) 16 | 17 | const handleScrollTop = () => { 18 | window.scrollTo({ top: 0 }) 19 | } 20 | const handleScrollToComment = () => { 21 | document.getElementById('comment').scrollIntoView() 22 | } 23 | return ( 24 | 56 | ) 57 | } 58 | 59 | export default ScrollTopAndComment 60 | -------------------------------------------------------------------------------- /blog/components/SectionContainer.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | 3 | interface Props { 4 | children: ReactNode 5 | } 6 | 7 | export default function SectionContainer({ children }: Props) { 8 | return ( 9 |
{children}
10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /blog/components/Tag.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import { kebabCase } from 'pliny/utils/kebabCase' 3 | 4 | interface Props { 5 | text: string 6 | } 7 | 8 | const Tag = ({ text }: Props) => { 9 | return ( 10 | 14 | {text.split(' ').join('-')} 15 | 16 | ) 17 | } 18 | 19 | export default Tag 20 | -------------------------------------------------------------------------------- /blog/components/ThemeSwitch.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import { useTheme } from 'next-themes' 3 | 4 | const ThemeSwitch = () => { 5 | const [mounted, setMounted] = useState(false) 6 | const { theme, setTheme, resolvedTheme } = useTheme() 7 | 8 | // When mounted on client, now we can show the UI 9 | useEffect(() => setMounted(true), []) 10 | return null 11 | return ( 12 | 34 | ) 35 | } 36 | 37 | export default ThemeSwitch 38 | -------------------------------------------------------------------------------- /blog/components/social-icons/facebook.svg: -------------------------------------------------------------------------------- 1 | Facebook icon -------------------------------------------------------------------------------- /blog/components/social-icons/github.svg: -------------------------------------------------------------------------------- 1 | GitHub icon -------------------------------------------------------------------------------- /blog/components/social-icons/index.tsx: -------------------------------------------------------------------------------- 1 | import Mail from './mail.svg' 2 | import Github from './github.svg' 3 | import Facebook from './facebook.svg' 4 | import Youtube from './youtube.svg' 5 | import Linkedin from './linkedin.svg' 6 | import Twitter from './twitter.svg' 7 | 8 | // Icons taken from: https://simpleicons.org/ 9 | 10 | const components = { 11 | mail: Mail, 12 | github: Github, 13 | facebook: Facebook, 14 | youtube: Youtube, 15 | linkedin: Linkedin, 16 | twitter: Twitter, 17 | } 18 | 19 | const SocialIcon = ({ kind, href, size = 8 }) => { 20 | if (!href || (kind === 'mail' && !/^mailto:\w+([.-]?\w+)@\w+([.-]?\w+)(.\w{2,3})+$/.test(href))) 21 | return null 22 | 23 | const SocialSvg = components[kind] 24 | 25 | return ( 26 |
32 | {kind} 33 | 36 | 37 | ) 38 | } 39 | 40 | export default SocialIcon 41 | -------------------------------------------------------------------------------- /blog/components/social-icons/linkedin.svg: -------------------------------------------------------------------------------- 1 | LinkedIn icon -------------------------------------------------------------------------------- /blog/components/social-icons/mail.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /blog/components/social-icons/twitter.svg: -------------------------------------------------------------------------------- 1 | Twitter icon -------------------------------------------------------------------------------- /blog/components/social-icons/youtube.svg: -------------------------------------------------------------------------------- 1 | YouTube icon -------------------------------------------------------------------------------- /blog/data/authors/default.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: yourname 3 | avatar: /blog/static/images/default.jpg 4 | occupation: Founder of yourcompany 5 | company: yourcompany 6 | email: support@yourdomain.com 7 | linkedin: https://www.linkedin.com/in/yourlinkedin/ 8 | --- 9 | 10 | As yourcompanyname's CEO, I am proud to offer a top-notch Lead Generation platform to our global customers. Our comprehensive Go-To-Market Intelligence Solution provides unparalleled support to sales, marketing. With seamless integrations built into workflows and technology stacks, including the leading Lead Generation Platform, yourcompanyname helps drive predictable, accelerated, and sustainable growth. 11 | 12 | Our platform combines cutting-edge technology with unmatched data coverage and accuracy. We prioritize GDPR and CCPA compliance to protect our customers' data. Let yourcompanyname help you find your ideal prospects and convert them into loyal customers. Experience the power of yourcompanyname at https://www.yourdomain.com/. -------------------------------------------------------------------------------- /blog/data/headerNavLinks.ts: -------------------------------------------------------------------------------- 1 | const headerNavLinks = [ 2 | { href: '/', title: 'Blog' }, 3 | { href: '/tags/', title: 'Tags' }, 4 | { href: '/projects/', title: 'Projects' }, 5 | // { href: '/about', title: 'About' }, 6 | ] 7 | 8 | export default headerNavLinks 9 | -------------------------------------------------------------------------------- /blog/data/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /blog/data/projectsData.ts: -------------------------------------------------------------------------------- 1 | const projectsData = [ 2 | { 3 | title: 'yourcompanyname', 4 | description: `yourdomain.com is a lead generation software that helps sales people reach out to the right prospects, close more deals, and increase revenue at scale.`, 5 | imgSrc: '/blog/static/images/logo.png', 6 | href: 'https://www.yourdomain.com/', 7 | } 8 | ] 9 | 10 | export default projectsData -------------------------------------------------------------------------------- /blog/data/references-data.bib: -------------------------------------------------------------------------------- 1 | @article{Nash1950, 2 | title={Equilibrium points in n-person games}, 3 | author={Nash, John}, 4 | journal={Proceedings of the national academy of sciences}, 5 | volume={36}, 6 | number={1}, 7 | pages={48--49}, 8 | year={1950}, 9 | publisher={USA} 10 | } 11 | 12 | @article{Nash1951, 13 | title={Non-cooperative games}, 14 | author={Nash, John}, 15 | journal={Annals of mathematics}, 16 | pages={286--295}, 17 | year={1951}, 18 | publisher={JSTOR} 19 | } 20 | 21 | @Manual{Macfarlane2006, 22 | url={https://pandoc.org/}, 23 | title={Pandoc: a universal document converter}, 24 | author={MacFarlane, John}, 25 | year={2006} 26 | } 27 | 28 | @book{Xie2016, 29 | title={Bookdown: authoring books and technical documents with R markdown}, 30 | author={Xie, Yihui}, 31 | year={2016}, 32 | publisher={CRC Press} 33 | } -------------------------------------------------------------------------------- /blog/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/components/*": ["components/*"], 6 | "@/data/*": ["data/*"], 7 | "@/layouts/*": ["layouts/*"], 8 | "@/lib/*": ["lib/*"], 9 | "@/css/*": ["css/*"], 10 | "contentlayer/generated": ["./.contentlayer/generated"] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /blog/layouts/AuthorLayout.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | import type { Authors } from 'contentlayer/generated' 3 | import SocialIcon from '@/components/social-icons' 4 | import Image from '@/components/Image' 5 | import { PageSEO } from '@/components/SEO' 6 | 7 | interface Props { 8 | children: ReactNode 9 | content: Omit 10 | } 11 | 12 | export default function AuthorLayout({ children, content }: Props) { 13 | const { name, avatar, occupation, company, email, twitter, linkedin, github } = content 14 | 15 | return ( 16 | <> 17 | 18 |
19 |
20 |

21 | About 22 |

23 |
24 |
25 |
26 | avatar 33 |

{name}

34 |
{occupation}
35 |
{company}
36 |
37 | 38 | 39 | 40 | 41 |
42 |
43 |
{children}
44 |
45 |
46 | 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /blog/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /blog/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import Link from '@/components/Link' 2 | import { PageSEO } from '@/components/SEO' 3 | 4 | export default function FourZeroFour() { 5 | return ( 6 | <> 7 | 8 |
9 |
10 |

11 | 404 12 |

13 |
14 |
15 |

16 | Sorry we couldn't find this page. 17 |

18 |

19 | But dont worry, you can find plenty of other things on our homepage. 20 |

21 | 25 | Back to homepage 26 | 27 |
28 |
29 | 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /blog/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '@/css/tailwind.css' 2 | import '@/css/prism.css' 3 | import 'katex/dist/katex.css' 4 | // import '@/css/docsearch.css' // Uncomment if using algolia docsearch 5 | // import '@docsearch/css' // Uncomment if using algolia docsearch 6 | 7 | import { ThemeProvider } from 'next-themes' 8 | import type { AppProps } from 'next/app' 9 | import Head from 'next/head' 10 | 11 | import siteMetadata from '@/data/siteMetadata' 12 | import { Analytics } from 'pliny/analytics' 13 | import { SearchProvider } from 'pliny/search' 14 | import LayoutWrapper from '@/components/LayoutWrapper' 15 | 16 | export default function App({ Component, pageProps }: AppProps) { 17 | return ( 18 | 19 | 20 | 21 | 22 |