├── db.sqlite3 ├── apps ├── __init__.py ├── comments │ ├── router.py │ ├── urls.py │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── apps.py │ ├── services.py │ ├── admin.py │ └── models.py ├── core │ ├── __init__.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ ├── populate_posts.py │ │ │ ├── populate_tags.py │ │ │ └── populate_groups.py │ ├── migrations │ │ └── __init__.py │ ├── admin.py │ ├── tests.py │ ├── apps.py │ ├── services.py │ ├── serializers.py │ ├── models.py │ └── views.py ├── groups │ ├── __init__.py │ ├── models.py │ ├── views.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0001_initial.py │ │ ├── 0003_added_group_rules.py │ │ ├── 0005_added_group_invites.py │ │ └── 0004_added_group_invites.py │ ├── tests.py │ ├── models │ │ ├── __init__.py │ │ ├── member_request.py │ │ ├── rules.py │ │ ├── invite.py │ │ └── group.py │ ├── urls.py │ ├── views │ │ ├── __init__.py │ │ └── member.py │ ├── apps.py │ ├── filters.py │ ├── serializers │ │ ├── __init__.py │ │ ├── rules.py │ │ ├── invite.py │ │ ├── member.py │ │ └── member_request.py │ ├── router.py │ ├── signals.py │ ├── services.py │ └── permissions.py ├── media │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ ├── admin.py │ ├── views.py │ └── apps.py ├── posts │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0002_altered_post_model.py │ │ └── 0003_added_post_vote_model.py │ ├── tests.py │ ├── apps.py │ ├── urls.py │ ├── filters.py │ └── router.py ├── tags │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0003_added_type_in_tag.py │ │ ├── 0001_initial.py │ │ └── 0002_added_tag_type.py │ ├── tests.py │ ├── apps.py │ ├── urls.py │ ├── router.py │ ├── filters.py │ ├── views.py │ ├── admin.py │ ├── serializers.py │ └── models.py ├── bookmarks │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── apps.py │ ├── abstracts.py │ ├── admin.py │ ├── models.py │ └── serializers.py ├── followers │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── apps.py │ ├── abstracts.py │ ├── serializers.py │ ├── admin.py │ └── models.py ├── locations │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── admin.py │ ├── tests.py │ ├── views.py │ └── apps.py ├── profiles │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0001_initial.py │ │ └── 0002_auto_20220911_1305.py │ ├── admin.py │ ├── tests.py │ ├── apps.py │ ├── urls.py │ ├── router.py │ ├── serializers.py │ └── models.py └── reports │ ├── __init__.py │ ├── migrations │ ├── __init__.py │ └── 0001_report_type_model.py │ ├── models.py │ ├── tests.py │ ├── views.py │ ├── apps.py │ ├── models │ ├── __init__.py │ ├── types.py │ ├── post.py │ └── user.py │ ├── views │ ├── __init__.py │ └── types.py │ ├── urls.py │ ├── router.py │ ├── serializers │ ├── __init__.py │ ├── types.py │ ├── post.py │ └── user.py │ └── abstracts.py ├── static ├── frontend │ └── reddit-app │ │ ├── README.md │ │ ├── angular │ │ ├── src │ │ │ ├── assets │ │ │ │ ├── .gitkeep │ │ │ │ ├── images │ │ │ │ │ └── default_user.png │ │ │ │ └── favicon │ │ │ │ │ ├── favicon-16x16.png │ │ │ │ │ ├── favicon-32x32.png │ │ │ │ │ ├── apple-touch-icon.png │ │ │ │ │ ├── android-chrome-192x192.png │ │ │ │ │ ├── android-chrome-512x512.png │ │ │ │ │ └── site.webmanifest │ │ │ ├── app │ │ │ │ ├── search │ │ │ │ │ ├── search.component.scss │ │ │ │ │ ├── search.component.spec.ts │ │ │ │ │ └── search.component.html │ │ │ │ ├── setttings │ │ │ │ │ ├── setttings.component.scss │ │ │ │ │ ├── setttings.component.html │ │ │ │ │ ├── setttings.component.ts │ │ │ │ │ └── setttings.component.spec.ts │ │ │ │ ├── auth │ │ │ │ │ ├── sign-out │ │ │ │ │ │ ├── sign-out.component.scss │ │ │ │ │ │ ├── sign-out.component.html │ │ │ │ │ │ ├── sign-out.component.spec.ts │ │ │ │ │ │ └── sign-out.component.ts │ │ │ │ │ ├── sign-in │ │ │ │ │ │ └── sign-in.component.spec.ts │ │ │ │ │ └── sign-up │ │ │ │ │ │ └── sign-up.component.spec.ts │ │ │ │ ├── comments │ │ │ │ │ ├── comment-list │ │ │ │ │ │ ├── comment-list.component.scss │ │ │ │ │ │ ├── comment-list.component.html │ │ │ │ │ │ ├── comment-list.component.spec.ts │ │ │ │ │ │ └── comment-list.component.ts │ │ │ │ │ ├── comment-create │ │ │ │ │ │ ├── comment-create.component.scss │ │ │ │ │ │ ├── comment-create.component.html │ │ │ │ │ │ ├── comment-create.component.spec.ts │ │ │ │ │ │ └── comment-create.component.ts │ │ │ │ │ ├── comment-group │ │ │ │ │ │ ├── comment-group.component.scss │ │ │ │ │ │ └── comment-group.component.spec.ts │ │ │ │ │ ├── comment-user │ │ │ │ │ │ ├── comment-user.component.html │ │ │ │ │ │ ├── comment-user.component.ts │ │ │ │ │ │ ├── comment-user.component.spec.ts │ │ │ │ │ │ └── comment-user.component.scss │ │ │ │ │ ├── comment │ │ │ │ │ │ ├── comment.component.spec.ts │ │ │ │ │ │ └── comment.component.scss │ │ │ │ │ ├── comment-edit │ │ │ │ │ │ ├── comment-edit.component.spec.ts │ │ │ │ │ │ └── comment-edit.component.html │ │ │ │ │ └── comment-footer │ │ │ │ │ │ ├── comment-footer.component.spec.ts │ │ │ │ │ │ ├── comment-footer.component.scss │ │ │ │ │ │ └── comment-footer.component.html │ │ │ │ ├── group │ │ │ │ │ ├── group-router │ │ │ │ │ │ ├── group-router.component.scss │ │ │ │ │ │ ├── group-router.component.html │ │ │ │ │ │ ├── group-router.component.ts │ │ │ │ │ │ └── group-router.component.spec.ts │ │ │ │ │ ├── group-post │ │ │ │ │ │ ├── group-post.component.html │ │ │ │ │ │ ├── group-post.component.scss │ │ │ │ │ │ └── group-post.component.spec.ts │ │ │ │ │ ├── group-feed │ │ │ │ │ │ ├── group-feed.component.scss │ │ │ │ │ │ ├── group-feed.component.spec.ts │ │ │ │ │ │ └── group-feed.component.html │ │ │ │ │ ├── create-group │ │ │ │ │ │ ├── create-group.component.scss │ │ │ │ │ │ └── create-group.component.spec.ts │ │ │ │ │ ├── group-search │ │ │ │ │ │ ├── group-search.component.scss │ │ │ │ │ │ └── group-search.component.spec.ts │ │ │ │ │ └── group │ │ │ │ │ │ └── group.component.spec.ts │ │ │ │ ├── profiles │ │ │ │ │ ├── profile-history │ │ │ │ │ │ ├── profile-history.component.scss │ │ │ │ │ │ ├── profile-history.component.html │ │ │ │ │ │ ├── profile-history.component.ts │ │ │ │ │ │ └── profile-history.component.spec.ts │ │ │ │ │ ├── profile-posts │ │ │ │ │ │ ├── profile-posts.component.scss │ │ │ │ │ │ ├── profile-posts.component.spec.ts │ │ │ │ │ │ └── profile-posts.component.html │ │ │ │ │ ├── profile-bookmarks │ │ │ │ │ │ ├── profile-bookmarks.component.scss │ │ │ │ │ │ ├── profile-bookmarks.component.html │ │ │ │ │ │ ├── profile-bookmarks.component.spec.ts │ │ │ │ │ │ └── profile-bookmarks.component.ts │ │ │ │ │ ├── profile-overview │ │ │ │ │ │ ├── profile-overview.component.scss │ │ │ │ │ │ ├── profile-overview.component.html │ │ │ │ │ │ ├── profile-overview.component.ts │ │ │ │ │ │ └── profile-overview.component.spec.ts │ │ │ │ │ ├── add-interest-dialog │ │ │ │ │ │ ├── add-interest-dialog.component.scss │ │ │ │ │ │ ├── add-interest-dialog.component.html │ │ │ │ │ │ ├── add-interest-dialog.component.ts │ │ │ │ │ │ └── add-interest-dialog.component.spec.ts │ │ │ │ │ ├── profile-upvotes │ │ │ │ │ │ ├── profile-upvotes.component.scss │ │ │ │ │ │ ├── profile-upvotes.component.html │ │ │ │ │ │ └── profile-upvotes.component.spec.ts │ │ │ │ │ ├── profile-downvotes │ │ │ │ │ │ ├── profile-downvotes.component.scss │ │ │ │ │ │ ├── profile-downvotes.component.html │ │ │ │ │ │ ├── profile-downvotes.component.spec.ts │ │ │ │ │ │ └── profile-downvotes.component.ts │ │ │ │ │ ├── profile-comments │ │ │ │ │ │ ├── profile-comments.component.scss │ │ │ │ │ │ ├── profile-comments.component.html │ │ │ │ │ │ ├── profile-comments.component.ts │ │ │ │ │ │ └── profile-comments.component.spec.ts │ │ │ │ │ └── profiles │ │ │ │ │ │ ├── profiles.component.spec.ts │ │ │ │ │ │ └── profiles.component.scss │ │ │ │ ├── core │ │ │ │ │ ├── models │ │ │ │ │ │ ├── report.model.ts │ │ │ │ │ │ ├── user.model.ts │ │ │ │ │ │ ├── tag.model.ts │ │ │ │ │ │ ├── group.model.ts │ │ │ │ │ │ ├── post.model.ts │ │ │ │ │ │ └── comment.model.ts │ │ │ │ │ ├── pipes │ │ │ │ │ │ └── safe-content │ │ │ │ │ │ │ ├── safe-content.pipe.spec.ts │ │ │ │ │ │ │ └── safe-content.pipe.ts │ │ │ │ │ ├── guards │ │ │ │ │ │ └── auth │ │ │ │ │ │ │ ├── auth.guard.spec.ts │ │ │ │ │ │ │ └── auth.guard.ts │ │ │ │ │ └── services │ │ │ │ │ │ ├── post │ │ │ │ │ │ └── post.service.spec.ts │ │ │ │ │ │ ├── user │ │ │ │ │ │ └── user.service.spec.ts │ │ │ │ │ │ ├── group │ │ │ │ │ │ └── group.service.spec.ts │ │ │ │ │ │ ├── report │ │ │ │ │ │ ├── report.service.spec.ts │ │ │ │ │ │ └── report.service.ts │ │ │ │ │ │ ├── comment │ │ │ │ │ │ └── comment.service.spec.ts │ │ │ │ │ │ └── storage │ │ │ │ │ │ └── storage-handler.service.spec.ts │ │ │ │ ├── components │ │ │ │ │ ├── confirmation-dialog │ │ │ │ │ │ ├── confirmation-dialog.component.scss │ │ │ │ │ │ ├── confirmation-dialog.component.ts │ │ │ │ │ │ ├── confirmation-dialog.component.spec.ts │ │ │ │ │ │ └── confirmation-dialog.component.html │ │ │ │ │ └── report-dialog │ │ │ │ │ │ ├── report-dialog.component.html │ │ │ │ │ │ ├── report-dialog.component.spec.ts │ │ │ │ │ │ └── report-dialog.component.scss │ │ │ │ ├── feed │ │ │ │ │ ├── feed.component.scss │ │ │ │ │ └── feed.component.spec.ts │ │ │ │ ├── post │ │ │ │ │ ├── post-loader │ │ │ │ │ │ ├── post-loader.component.ts │ │ │ │ │ │ ├── post-loader.component.spec.ts │ │ │ │ │ │ └── post-loader.component.html │ │ │ │ │ ├── post-detail │ │ │ │ │ │ ├── post-detail.component.scss │ │ │ │ │ │ ├── post-detail.component.spec.ts │ │ │ │ │ │ └── post-detail.component.html │ │ │ │ │ ├── post │ │ │ │ │ │ └── post.component.spec.ts │ │ │ │ │ └── create-post │ │ │ │ │ │ ├── create-post.component.spec.ts │ │ │ │ │ │ └── create-post.component.scss │ │ │ │ ├── auth.interceptor.ts │ │ │ │ ├── auth.header.interceptor.ts │ │ │ │ └── app.component.spec.ts │ │ │ ├── favicon.ico │ │ │ ├── environments │ │ │ │ ├── environment.ts │ │ │ │ └── environment.prod.ts │ │ │ ├── main.ts │ │ │ ├── test.ts │ │ │ └── index.html │ │ ├── .editorconfig │ │ ├── e2e │ │ │ ├── src │ │ │ │ ├── app.po.ts │ │ │ │ └── app.e2e-spec.ts │ │ │ ├── tsconfig.json │ │ │ └── protractor.conf.js │ │ ├── tsconfig.app.json │ │ ├── tsconfig.spec.json │ │ ├── tsconfig.json │ │ ├── .browserslistrc │ │ ├── README.md │ │ └── karma.conf.js │ │ ├── react-app │ │ ├── tsconfig.json │ │ ├── src │ │ │ ├── main.tsx │ │ │ ├── components │ │ │ │ └── auth │ │ │ │ │ └── SignOut │ │ │ │ │ ├── SignOut.scss │ │ │ │ │ └── SignOut.tsx │ │ │ ├── App.tsx │ │ │ ├── App.css │ │ │ └── index.css │ │ ├── vite.config.ts │ │ ├── index.html │ │ ├── eslint.config.js │ │ ├── tsconfig.node.json │ │ ├── tsconfig.app.json │ │ ├── package.json │ │ └── public │ │ │ └── vite.svg │ │ └── .gitignore ├── templates │ ├── emails │ │ └── report_initiated_mail.html │ ├── home.html │ └── base.html └── assets │ ├── images │ └── default_user.png │ └── favicon │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ └── site.webmanifest ├── .dockerignore ├── Procfile ├── screenshots ├── image01.png ├── image02.png ├── image03.png ├── image04.png ├── image05.png ├── image06.png └── image07.png ├── docker-compose.yml ├── pyproject.toml ├── commands.txt ├── .flake8 ├── reddit_clone ├── asgi.py ├── wsgi.py ├── __init__.py └── settings │ ├── development.py │ └── production.py ├── groups.csv ├── Dockerfile ├── env_settings.py ├── manage.py ├── .env.sample ├── .github └── workflows │ ├── django.yml │ └── deploy.yml ├── requirements.txt └── tags.csv /db.sqlite3: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/comments/router.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/comments/urls.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/groups/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/groups/models.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/groups/views.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/media/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/posts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/tags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/bookmarks/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/comments/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/followers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/locations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/profiles/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/reports/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/core/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/core/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/media/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/posts/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/tags/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/bookmarks/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/comments/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/followers/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/groups/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/locations/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/profiles/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/reports/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/core/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | venv 2 | .git 3 | .gitignore 4 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn reddit_clone.wsgi 2 | -------------------------------------------------------------------------------- /static/templates/emails/report_initiated_mail.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/search/search.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/setttings/setttings.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/auth/sign-out/sign-out.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/core/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /apps/core/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /apps/groups/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /apps/locations/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /apps/media/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /apps/media/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /apps/posts/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /apps/reports/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /apps/reports/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /apps/tags/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment-list/comment-list.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/group/group-router/group-router.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/locations/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /apps/locations/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /apps/locations/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /apps/media/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /apps/media/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /apps/profiles/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /apps/profiles/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /apps/reports/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment-create/comment-create.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-history/profile-history.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-posts/profile-posts.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-bookmarks/profile-bookmarks.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-overview/profile-overview.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /screenshots/image01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhvi-n/django-reddit/HEAD/screenshots/image01.png -------------------------------------------------------------------------------- /screenshots/image02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhvi-n/django-reddit/HEAD/screenshots/image02.png -------------------------------------------------------------------------------- /screenshots/image03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhvi-n/django-reddit/HEAD/screenshots/image03.png -------------------------------------------------------------------------------- /screenshots/image04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhvi-n/django-reddit/HEAD/screenshots/image04.png -------------------------------------------------------------------------------- /screenshots/image05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhvi-n/django-reddit/HEAD/screenshots/image05.png -------------------------------------------------------------------------------- /screenshots/image06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhvi-n/django-reddit/HEAD/screenshots/image06.png -------------------------------------------------------------------------------- /screenshots/image07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhvi-n/django-reddit/HEAD/screenshots/image07.png -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/add-interest-dialog/add-interest-dialog.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/setttings/setttings.component.html: -------------------------------------------------------------------------------- 1 |

setttings works!

2 | -------------------------------------------------------------------------------- /apps/media/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MediaConfig(AppConfig): 5 | name = "media" 6 | -------------------------------------------------------------------------------- /apps/tags/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TagsConfig(AppConfig): 5 | name = "tags" 6 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/group/group-router/group-router.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /apps/profiles/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ProfilesConfig(AppConfig): 5 | name = "profiles" 6 | -------------------------------------------------------------------------------- /apps/reports/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ReportsConfig(AppConfig): 5 | name = "reports" 6 | -------------------------------------------------------------------------------- /static/assets/images/default_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhvi-n/django-reddit/HEAD/static/assets/images/default_user.png -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-history/profile-history.component.html: -------------------------------------------------------------------------------- 1 |

profile-history works!

2 | -------------------------------------------------------------------------------- /apps/locations/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class LocationsConfig(AppConfig): 5 | name = "locations" 6 | -------------------------------------------------------------------------------- /apps/reports/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .post import PostReport 2 | from .types import ReportType 3 | from .user import UserProfileReport 4 | -------------------------------------------------------------------------------- /static/assets/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhvi-n/django-reddit/HEAD/static/assets/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /static/assets/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhvi-n/django-reddit/HEAD/static/assets/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-overview/profile-overview.component.html: -------------------------------------------------------------------------------- 1 |

profile-overview works!

2 | -------------------------------------------------------------------------------- /static/assets/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhvi-n/django-reddit/HEAD/static/assets/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/core/models/report.model.ts: -------------------------------------------------------------------------------- 1 | export class Report { 2 | id: number; 3 | user: number; 4 | } 5 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/group/group-post/group-post.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/add-interest-dialog/add-interest-dialog.component.html: -------------------------------------------------------------------------------- 1 |

add-interest-dialog works!

2 | -------------------------------------------------------------------------------- /apps/reports/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .post import PostReportViewSet 2 | from .types import ReportTypeViewSet 3 | from .user import UserProfileReportViewSet 4 | -------------------------------------------------------------------------------- /static/assets/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhvi-n/django-reddit/HEAD/static/assets/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /static/assets/favicon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhvi-n/django-reddit/HEAD/static/assets/favicon/android-chrome-512x512.png -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhvi-n/django-reddit/HEAD/static/frontend/reddit-app/angular/src/favicon.ico -------------------------------------------------------------------------------- /apps/tags/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import include, path 2 | from tags.router import router 3 | 4 | urlpatterns = [ 5 | path("api/v1/", include(router.urls)), 6 | ] 7 | -------------------------------------------------------------------------------- /apps/core/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CoreConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "core" 7 | -------------------------------------------------------------------------------- /apps/posts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PostsConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "posts" 7 | -------------------------------------------------------------------------------- /apps/reports/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import include, path, re_path 2 | from reports.router import router 3 | 4 | urlpatterns = [ 5 | path("api/v1/", include(router.urls)), 6 | ] 7 | -------------------------------------------------------------------------------- /apps/comments/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CommentsConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "comments" 7 | -------------------------------------------------------------------------------- /apps/tags/router.py: -------------------------------------------------------------------------------- 1 | from rest_framework_nested import routers 2 | from tags.views import TagViewSet 3 | 4 | router = routers.SimpleRouter() 5 | 6 | router.register(r"tags", TagViewSet) 7 | -------------------------------------------------------------------------------- /apps/bookmarks/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BookmarksConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "bookmarks" 7 | -------------------------------------------------------------------------------- /apps/followers/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class FollowersConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "followers" 7 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/assets/images/default_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhvi-n/django-reddit/HEAD/static/frontend/reddit-app/angular/src/assets/images/default_user.png -------------------------------------------------------------------------------- /static/frontend/reddit-app/react-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/assets/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhvi-n/django-reddit/HEAD/static/frontend/reddit-app/angular/src/assets/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/assets/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhvi-n/django-reddit/HEAD/static/frontend/reddit-app/angular/src/assets/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/assets/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhvi-n/django-reddit/HEAD/static/frontend/reddit-app/angular/src/assets/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /apps/groups/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .group import Group 2 | from .invite import GroupInvite 3 | from .member import GroupMember 4 | from .member_request import MemberRequest 5 | from .rules import GroupRule 6 | -------------------------------------------------------------------------------- /apps/reports/router.py: -------------------------------------------------------------------------------- 1 | from reports.views import ReportTypeViewSet 2 | from rest_framework_nested import routers 3 | 4 | router = routers.SimpleRouter() 5 | 6 | router.register("report_types", ReportTypeViewSet) 7 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/assets/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhvi-n/django-reddit/HEAD/static/frontend/reddit-app/angular/src/assets/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/assets/favicon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madhvi-n/django-reddit/HEAD/static/frontend/reddit-app/angular/src/assets/favicon/android-chrome-512x512.png -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | web: 4 | build: . 5 | ports: 6 | - "8000:8000" 7 | command: python manage.py runserver 0.0.0.0:8000 8 | volumes: 9 | - .:/usr/src/app 10 | -------------------------------------------------------------------------------- /apps/reports/serializers/__init__.py: -------------------------------------------------------------------------------- 1 | from .post import PostReportLightSerializer, PostReportSerializer 2 | from .types import ReportTypeSerializer 3 | from .user import UserProfileReportLightSerializer, UserProfileReportSerializer 4 | -------------------------------------------------------------------------------- /apps/groups/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import include, path 2 | 3 | from .router import group_router, router 4 | 5 | urlpatterns = [ 6 | path("api/v1/", include(router.urls)), 7 | path("api/v1/", include(group_router.urls)), 8 | ] 9 | -------------------------------------------------------------------------------- /apps/posts/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import include, path 2 | 3 | from .router import post_router, router 4 | 5 | urlpatterns = [ 6 | path("api/v1/", include(router.urls)), 7 | path("api/v1/", include(post_router.urls)), 8 | ] 9 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/core/models/user.model.ts: -------------------------------------------------------------------------------- 1 | export class User { 2 | id: number; 3 | username: string; 4 | first_name: string; 5 | last_name: string; 6 | email?: string; 7 | date_joined?: string; 8 | } 9 | -------------------------------------------------------------------------------- /apps/groups/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .group import GroupViewSet 2 | from .invite import GroupInviteViewSet 3 | from .member import GroupMemberViewSet 4 | from .member_request import MemberRequestViewSet 5 | from .rules import GroupRuleViewSet 6 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/core/models/tag.model.ts: -------------------------------------------------------------------------------- 1 | export class TagType { 2 | id: number; 3 | title: string; 4 | } 5 | 6 | export class Tag { 7 | id: number; 8 | name: string; 9 | tag_type: TagType; 10 | } 11 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/components/confirmation-dialog/confirmation-dialog.component.scss: -------------------------------------------------------------------------------- 1 | ::ng-deep .cdk-global-scrollblock { 2 | position: static; 3 | } 4 | 5 | .end-container { 6 | justify-content: flex-end!important; 7 | } 8 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/feed/feed.component.scss: -------------------------------------------------------------------------------- 1 | .related-section { 2 | background-color: white; 3 | min-height: 300px; 4 | margin-top: 16px; 5 | border: 1px solid #dedede; 6 | border-radius: 8px; 7 | padding: 16px; 8 | } 9 | -------------------------------------------------------------------------------- /apps/groups/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class GroupsConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "groups" 7 | 8 | def ready(self): 9 | import groups.signals 10 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 88 3 | target-version = ['py312'] 4 | exclude = ''' 5 | /( 6 | \.git 7 | | \.venv 8 | | venv 9 | | env 10 | | migrations 11 | | __pycache__ 12 | | build 13 | | dist 14 | )/ 15 | ''' 16 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/group/group-post/group-post.component.scss: -------------------------------------------------------------------------------- 1 | .create-group-post { 2 | box-shadow: none; 3 | border: 1px solid #dedede; 4 | border-radius: 8px; 5 | display: flex; 6 | padding: 0; 7 | width: 100%; 8 | margin: 16px 0; 9 | } 10 | -------------------------------------------------------------------------------- /static/assets/favicon/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /apps/reports/views/types.py: -------------------------------------------------------------------------------- 1 | from core.views import BaseReadOnlyViewSet 2 | from reports.models import ReportType 3 | from reports.serializers import ReportTypeSerializer 4 | 5 | 6 | class ReportTypeViewSet(BaseReadOnlyViewSet): 7 | serializer_class = ReportTypeSerializer 8 | queryset = ReportType.objects.all() 9 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: false, 3 | baseUrl: '/api/v1/', 4 | serverUrl: 'http://localhost:8000', 5 | appUrl: 'http://localhost:4200', 6 | loginUrl: 'http://localhost:4200/sign-in', 7 | staticUrl: '../assets/images/' 8 | }; 9 | -------------------------------------------------------------------------------- /apps/tags/filters.py: -------------------------------------------------------------------------------- 1 | import django_filters 2 | from tags.models import Tag 3 | 4 | 5 | class TagFilterSet(django_filters.rest_framework.FilterSet): 6 | tag_type__title = django_filters.CharFilter(lookup_expr="iexact") 7 | 8 | class Meta: 9 | model = Tag 10 | fields = ("name", "tag_type") 11 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/core/pipes/safe-content/safe-content.pipe.spec.ts: -------------------------------------------------------------------------------- 1 | import { SafeContentPipe } from './safe-content.pipe'; 2 | 3 | describe('SafeContentPipe', () => { 4 | it('create an instance', () => { 5 | const pipe = new SafeContentPipe(); 6 | expect(pipe).toBeTruthy(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/assets/favicon/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /static/frontend/reddit-app/react-app/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import './index.css' 4 | import App from './App.tsx' 5 | 6 | createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /apps/reports/serializers/types.py: -------------------------------------------------------------------------------- 1 | from reports.models import ReportType 2 | from rest_framework import serializers 3 | 4 | 5 | class ReportTypeSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = ReportType 8 | fields = ("id", "title", "info") 9 | read_only_fields = ("id",) 10 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-bookmarks/profile-bookmarks.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/react-app/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [ 7 | react({ 8 | babel: { 9 | plugins: [['babel-plugin-react-compiler']], 10 | }, 11 | }), 12 | ], 13 | }) 14 | -------------------------------------------------------------------------------- /commands.txt: -------------------------------------------------------------------------------- 1 | USING docker 2 | docker build --tag reddit_clone:latest . 3 | docker image ls 4 | docker run --publish 8000:8000 reddit_clone 5 | 6 | ======================================================== 7 | 8 | USING docker compose 9 | docker compose up 10 | docker compose exec web python manage.py migrate 11 | docker compose exec web 12 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | baseUrl: '/api/v1/', 4 | serverUrl: 'https://django-reddit.onrender.com', 5 | appUrl: 'https://madhvi-n.github.io/django_reddit', 6 | loginUrl: 'https://madhvi-n.github.io/django_reddit/sign-in', 7 | staticUrl: '../assets/images/' 8 | }; 9 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | extend-ignore = 4 | E203, # Whitespace before ':', conflicts with Black 5 | W503, # Line break before binary operator, also conflicts with Black 6 | exclude = 7 | .git, 8 | __pycache__, 9 | venv, 10 | env, 11 | .venv, 12 | migrations, 13 | build, 14 | dist 15 | max-complexity = 10 16 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/group/group-feed/group-feed.component.scss: -------------------------------------------------------------------------------- 1 | 2 | .create-post-input { 3 | font-size: 16px; 4 | font-family: inherit; 5 | outline: none; 6 | border: 1px solid #dedede; 7 | border-radius: 8px; 8 | display: block; 9 | padding: 32px 16px; 10 | width: -webkit-fill-available; 11 | margin: 16px 0; 12 | resize: none; 13 | } 14 | -------------------------------------------------------------------------------- /apps/profiles/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import include, path, re_path 2 | from profiles.views import IsAuthenticatedView 3 | 4 | from .router import router 5 | 6 | urlpatterns = [ 7 | path("api/v1/", include(router.urls)), 8 | re_path( 9 | r"^api/v1/is_authenticated/$", 10 | IsAuthenticatedView.as_view(), 11 | name="is_authenticated", 12 | ), 13 | ] 14 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo(): Promise { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText(): Promise { 9 | return element(by.css('app-root .content span')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../out-tsc/e2e", 6 | "module": "commonjs", 7 | "target": "es2018", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /apps/tags/views.py: -------------------------------------------------------------------------------- 1 | from core.views import BaseViewSet 2 | from django.shortcuts import render 3 | 4 | from .filters import TagFilterSet 5 | from .models import Tag 6 | from .serializers import TagSerializer 7 | 8 | 9 | class TagViewSet(BaseViewSet): 10 | queryset = Tag.objects.all() 11 | serializer_class = TagSerializer 12 | filterset_class = TagFilterSet 13 | ordering = ["-created_at"] 14 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/auth/sign-out/sign-out.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

{{ context.title }}

4 |

5 | {{ context.subtitle }} 6 |

7 |
8 |
9 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-upvotes/profile-upvotes.component.scss: -------------------------------------------------------------------------------- 1 | .comment-item { 2 | margin: 8px 0; 3 | border: 1px solid #dedede; 4 | border-radius: 8px; 5 | display: inline-grid; 6 | background: white; 7 | padding: 16px; 8 | width: inherit; 9 | gap: 6px; 10 | color: #707070; 11 | 12 | .comment-subtext { 13 | font-size: 14px; 14 | color: #232323; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/setttings/setttings.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-setttings', 5 | templateUrl: './setttings.component.html', 6 | styleUrls: ['./setttings.component.scss'] 7 | }) 8 | export class SetttingsComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /apps/reports/models/types.py: -------------------------------------------------------------------------------- 1 | from core.models import TimeStampedModel 2 | from django.db import models 3 | 4 | 5 | class ReportType(TimeStampedModel): 6 | title = models.TextField(max_length=200) 7 | info = models.TextField(blank=True) 8 | 9 | class Meta: 10 | verbose_name = "Report Type" 11 | verbose_name_plural = "Report Types" 12 | 13 | def __str__(self): 14 | return f"{self.title}" 15 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-downvotes/profile-downvotes.component.scss: -------------------------------------------------------------------------------- 1 | .comment-item { 2 | margin: 8px 0; 3 | border: 1px solid #dedede; 4 | border-radius: 8px; 5 | display: inline-grid; 6 | background: white; 7 | padding: 16px; 8 | width: inherit; 9 | gap: 6px; 10 | color: #707070; 11 | 12 | .comment-subtext { 13 | font-size: 14px; 14 | color: #232323; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/followers/abstracts.py: -------------------------------------------------------------------------------- 1 | from core.models import TimeStampedModel 2 | from django.contrib.auth.models import User 3 | from django.db import models 4 | 5 | 6 | class AbstractFollower(TimeStampedModel): 7 | follower = models.ForeignKey( 8 | User, 9 | verbose_name="user", 10 | on_delete=models.CASCADE, 11 | related_name="%(class)s_followers", 12 | ) 13 | 14 | class Meta: 15 | abstract = True 16 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/group/group-router/group-router.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-group-router', 5 | templateUrl: './group-router.component.html', 6 | styleUrls: ['./group-router.component.scss'] 7 | }) 8 | export class GroupRouterComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment-group/comment-group.component.scss: -------------------------------------------------------------------------------- 1 | .separator-icon { 2 | font-size: 60px; 3 | height: 60px; 4 | color: #707070; 5 | text-align: center; 6 | width: auto; 7 | display: block; 8 | } 9 | 10 | .comment-header { 11 | width: 100%; 12 | padding: 8px 12px; 13 | font-size: 14px; 14 | } 15 | 16 | .link { 17 | font-weight: 500; 18 | color: brown; 19 | font-size: 14px; 20 | } 21 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/react-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Reddit 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /apps/bookmarks/abstracts.py: -------------------------------------------------------------------------------- 1 | from core.models import TimeStampedModel 2 | from django.contrib.auth.models import User 3 | from django.db import models 4 | 5 | 6 | class AbstractBookmark(TimeStampedModel): 7 | user = models.ForeignKey( 8 | User, 9 | verbose_name="user", 10 | on_delete=models.CASCADE, 11 | ) 12 | 13 | class Meta: 14 | abstract = True 15 | ordering = [ 16 | "created_at", 17 | ] 18 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-history/profile-history.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-profile-history', 5 | templateUrl: './profile-history.component.html', 6 | styleUrls: ['./profile-history.component.scss'] 7 | }) 8 | export class ProfileHistoryComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /apps/core/services.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.conf import settings 4 | from django.core.mail import send_mail 5 | from django.template import Context, Template 6 | 7 | 8 | def get_env_variable(var_name): 9 | try: 10 | return os.environ[var_name] 11 | except KeyError: 12 | error_msg = "Set the %s environment variable" % var_name 13 | return None 14 | 15 | 16 | def mail(subject, email_template, recipient, context): 17 | pass 18 | -------------------------------------------------------------------------------- /reddit_clone/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for reddit_clone 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/3.0/howto/deployment/asgi/ 8 | """ 9 | 10 | from django.core.asgi import get_asgi_application 11 | 12 | from env_settings import configure_environment 13 | 14 | configure_environment() 15 | application = get_asgi_application() 16 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-comments/profile-comments.component.scss: -------------------------------------------------------------------------------- 1 | .comment-item { 2 | margin: 8px 0; 3 | border: 1px solid #dedede; 4 | border-radius: 8px; 5 | display: inline-grid; 6 | background: white; 7 | padding: 16px; 8 | width: inherit; 9 | gap: 6px; 10 | color: #707070; 11 | width: -webkit-fill-available; 12 | 13 | .comment-subtext { 14 | font-size: 14px; 15 | color: #232323; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/core/guards/auth/auth.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { AuthGuard } from './auth.guard'; 4 | 5 | describe('AuthGuard', () => { 6 | let guard: AuthGuard; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | guard = TestBed.inject(AuthGuard); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(guard).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-overview/profile-overview.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-profile-overview', 5 | templateUrl: './profile-overview.component.html', 6 | styleUrls: ['./profile-overview.component.scss'] 7 | }) 8 | export class ProfileOverviewComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /groups.csv: -------------------------------------------------------------------------------- 1 | name, description, type, topic 2 | Django, Official Django Community for Django developers arounds the world, PRIVATE, Programming 3 | Angular, Official Angular Community for all frontend developers, PUBLIC, Programming 4 | Anime, Official Anime Community for all weebs, PUBLIC, Anime 5 | TravelHub, What's next on your bucket list? Know everything before you travel, PUBLIC, Travel 6 | Memes, Share your memes. Community for all who enjoy and want to share memes, PUBLIC, Memes 7 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/react-app/src/components/auth/SignOut/SignOut.scss: -------------------------------------------------------------------------------- 1 | .message-container { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | height: 100vh; 7 | } 8 | 9 | .width-100 { 10 | width: 100%; 11 | } 12 | 13 | .text-center { 14 | text-align: center; 15 | } 16 | 17 | .heading-1 { 18 | font-size: 2rem; 19 | font-weight: bold; 20 | } 21 | 22 | .heading-3 { 23 | font-size: 1.2rem; 24 | } 25 | -------------------------------------------------------------------------------- /apps/groups/filters.py: -------------------------------------------------------------------------------- 1 | import django_filters 2 | from groups.models import Group, GroupMember 3 | 4 | 5 | class GroupFilterSet(django_filters.rest_framework.FilterSet): 6 | class Meta: 7 | model = Group 8 | fields = ("group_type", "members__user", "members__member_type") 9 | 10 | 11 | class GroupMemberFilterSet(django_filters.rest_framework.FilterSet): 12 | class Meta: 13 | model = GroupMember 14 | fields = ("member_type", "group", "user") 15 | -------------------------------------------------------------------------------- /apps/posts/filters.py: -------------------------------------------------------------------------------- 1 | import django_filters 2 | from django.db import models 3 | from posts.models import Post 4 | 5 | 6 | class PostFilterSet(django_filters.FilterSet): 7 | title = django_filters.CharFilter(field_name="title", lookup_expr="contains") 8 | author = django_filters.CharFilter( 9 | field_name="author__username", lookup_expr="exact" 10 | ) 11 | 12 | class Meta: 13 | model = Post 14 | fields = ("title", "status", "group", "author") 15 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/add-interest-dialog/add-interest-dialog.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-add-interest-dialog', 5 | templateUrl: './add-interest-dialog.component.html', 6 | styleUrls: ['./add-interest-dialog.component.scss'] 7 | }) 8 | export class AddInterestDialogComponent implements OnInit { 9 | constructor() { } 10 | 11 | ngOnInit(): void { 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /reddit_clone/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for reddit_clone 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/3.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | from env_settings import configure_environment 15 | 16 | configure_environment() 17 | application = get_wsgi_application() 18 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/post/post-loader/post-loader.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-post-loader', 5 | templateUrl: './post-loader.component.html', 6 | styleUrls: ['./post-loader.component.scss'] 7 | }) 8 | export class PostLoaderComponent implements OnInit { 9 | @Input() detail: boolean = false; 10 | 11 | constructor() { } 12 | 13 | ngOnInit(): void { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # pull the official base image 2 | FROM python:3.8-bullseye 3 | 4 | # set work directory 5 | WORKDIR /usr/src/app 6 | 7 | # set environment variables 8 | ENV PYTHONDONTWRITEBYTECODE 1 9 | ENV PYTHONUNBUFFERED 1 10 | ENV SECRET_KEY 'secret_key' 11 | 12 | 13 | # install dependencies 14 | COPY ./requirements.txt /usr/src/app 15 | RUN pip install -r requirements.txt 16 | 17 | # copy project 18 | COPY . /usr/src/app 19 | 20 | CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] 21 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/core/services/post/post.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { PostService } from './post.service'; 4 | 5 | describe('PostService', () => { 6 | let service: PostService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(PostService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/core/services/user/user.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { UserService } from './user.service'; 4 | 5 | describe('UserService', () => { 6 | let service: UserService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(UserService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | import '@angular/compiler'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(AppModule) 13 | .catch(err => console.error(err)); 14 | -------------------------------------------------------------------------------- /reddit_clone/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # Simple env variable: development / production / test 4 | ENVIRONMENT = os.getenv("ENVIRONMENT", "development").lower() 5 | 6 | # Django settings modules 7 | SETTINGS_MAP = { 8 | "development": "reddit_clone.settings.development", 9 | "production": "reddit_clone.settings.production", 10 | } 11 | 12 | settings_module = SETTINGS_MAP.get(ENVIRONMENT, SETTINGS_MAP["development"]) 13 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings_module) 14 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/core/services/group/group.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { GroupService } from './group.service'; 4 | 5 | describe('GroupService', () => { 6 | let service: GroupService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(GroupService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/post/post-detail/post-detail.component.scss: -------------------------------------------------------------------------------- 1 | .post-card { 2 | margin: 16px 0; 3 | box-shadow: none; 4 | border: 1px solid #dedede; 5 | border-radius: 8px; 6 | display: flex; 7 | padding: 20px; 8 | flex-direction: column; 9 | } 10 | 11 | 12 | .post-content { 13 | padding-bottom: 20px 14 | } 15 | 16 | .separator-icon { 17 | font-size: 60px; 18 | height: 60px; 19 | color: #707070; 20 | text-align: center; 21 | width: auto; 22 | } 23 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/core/services/report/report.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ReportService } from './report.service'; 4 | 5 | describe('ReportService', () => { 6 | let service: ReportService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(ReportService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /env_settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def configure_environment(): 5 | environment = os.getenv("ENVIRONMENT", "development").lower() 6 | 7 | settings_map = { 8 | "development": "reddit_clone.settings.development", 9 | "production": "reddit_clone.settings.production", 10 | "test": "reddit_clone.settings.test", 11 | } 12 | 13 | os.environ.setdefault( 14 | "DJANGO_SETTINGS_MODULE", 15 | settings_map.get(environment, settings_map["development"]), 16 | ) 17 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/core/services/comment/comment.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { CommentService } from './comment.service'; 4 | 5 | describe('CommentService', () => { 6 | let service: CommentService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(CommentService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /apps/bookmarks/admin.py: -------------------------------------------------------------------------------- 1 | from bookmarks.models import PostBookmark 2 | from django.contrib import admin 3 | 4 | 5 | @admin.register(PostBookmark) 6 | class PostBookmarkAdmin(admin.ModelAdmin): 7 | list_display = ( 8 | "id", 9 | "created_at", 10 | "post", 11 | "user", 12 | ) 13 | list_filter = ( 14 | "created_at", 15 | "updated_at", 16 | "post", 17 | "user", 18 | ) 19 | raw_id_fields = ("user", "post") 20 | date_hierarchy = "created_at" 21 | -------------------------------------------------------------------------------- /apps/profiles/router.py: -------------------------------------------------------------------------------- 1 | from followers.views import UserFollowerViewSet 2 | from profiles.views import ProfileViewSet 3 | from reports.views import UserProfileReportViewSet 4 | from rest_framework_nested import routers 5 | 6 | router = routers.SimpleRouter() 7 | router.register(r"users", ProfileViewSet) 8 | user_profile_router = routers.NestedSimpleRouter(router, r"users", lookup="user") 9 | user_profile_router.register(r"reports", UserProfileReportViewSet) 10 | user_profile_router.register(r"followers", UserFollowerViewSet) 11 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-comments/profile-comments.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | {{ comment.created_at | timeSince }} ago 5 | {{ comment.comment }} 6 |
7 |
8 | 9 |

u/{{ user.username }} has no comments.

10 |
11 |
12 | -------------------------------------------------------------------------------- /apps/groups/serializers/__init__.py: -------------------------------------------------------------------------------- 1 | from .group import ( 2 | GroupCreateSerializer, 3 | GroupHeavySerializer, 4 | GroupReadOnlyLightSerializer, 5 | GroupReadOnlySerializer, 6 | GroupSerializer, 7 | ) 8 | from .invite import GroupInviteReadOnlySerializer, GroupInviteSerializer 9 | from .member import GroupMemberReadOnlySerializer, GroupMemberSerializer 10 | from .member_request import MemberRequestReadOnlySerializer, MemberRequestSerializer 11 | from .rules import GroupRuleReadOnlySerializer, GroupRuleSerializer 12 | -------------------------------------------------------------------------------- /apps/core/serializers.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from django.apps import apps 4 | from django.db.models.query import QuerySet 5 | from rest_framework import serializers 6 | 7 | 8 | class ModelReadOnlySerializer(serializers.ModelSerializer): 9 | def __init__(self, *args, **kwargs): 10 | super(ModelReadOnlySerializer, self).__init__(*args, **kwargs) 11 | 12 | meta = getattr(self, "Meta") 13 | meta.read_only_fields = meta.fields 14 | 15 | class Meta: 16 | read_only_fields = ("id",) 17 | 18 | pass 19 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/group/create-group/create-group.component.scss: -------------------------------------------------------------------------------- 1 | .create-group-section { 2 | margin: 8px 0; 3 | border: 1px solid #dedede; 4 | border-radius: 8px; 5 | display: flex; 6 | background: white; 7 | padding: 16px; 8 | width: auto; 9 | } 10 | 11 | .example-radio-group { 12 | display: flex; 13 | flex-direction: column; 14 | margin: 15px 0; 15 | } 16 | 17 | .example-radio-button { 18 | margin: 5px; 19 | } 20 | 21 | .sub-text { 22 | font-size: 12px; 23 | color: #707070; 24 | } 25 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment-list/comment-list.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 12 | 13 |
14 |
15 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/core/services/storage/storage-handler.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { StorageHandlerService } from './storage-handler.service'; 4 | 5 | describe('StorageHandlerService', () => { 6 | let service: StorageHandlerService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(StorageHandlerService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /apps/groups/serializers/rules.py: -------------------------------------------------------------------------------- 1 | from core.serializers import ModelReadOnlySerializer 2 | from groups.models import GroupRule 3 | from rest_framework import serializers 4 | 5 | 6 | class GroupRuleReadOnlySerializer(ModelReadOnlySerializer): 7 | class Meta: 8 | model = GroupRule 9 | fields = ("id", "group", "title", "rule_type", "description") 10 | 11 | 12 | class GroupRuleSerializer(serializers.ModelSerializer): 13 | class Meta: 14 | model = GroupRule 15 | fields = ("id", "group", "title", "rule_type", "description") 16 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/core/models/group.model.ts: -------------------------------------------------------------------------------- 1 | import { Tag } from './tag.model'; 2 | 3 | export class Rule { 4 | title: string; 5 | description: string; 6 | id: number; 7 | group: number; 8 | rule_type: string; 9 | } 10 | 11 | export class Group { 12 | id: number; 13 | name: string; 14 | description: string; 15 | group_type: string; 16 | archive_posts: boolean; 17 | topics?: Tag[]; 18 | rules?: Rule[]; 19 | created_at: string; 20 | updated_at?: string; 21 | members_count?: number; 22 | member_status?: any; 23 | } 24 | -------------------------------------------------------------------------------- /apps/reports/models/post.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from posts.models import Post 3 | from reports.abstracts import AbstractReport 4 | 5 | 6 | class PostReport(AbstractReport): 7 | post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="reports") 8 | 9 | class Meta: 10 | ordering = [ 11 | "-created_at", 12 | ] 13 | verbose_name = "Post Report" 14 | verbose_name_plural = "Post Reports" 15 | 16 | def __str__(self): 17 | return f"Report: {self.reported_user.username} by {self.reporter.username}" 18 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/auth.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; 2 | import { Observable } from 'rxjs'; 3 | import { Injectable } from '@angular/core'; 4 | 5 | @Injectable() 6 | export class AuthInterceptor implements HttpInterceptor { 7 | constructor() {} 8 | 9 | intercept(request: HttpRequest, next: HttpHandler): Observable> { 10 | request = request.clone({ 11 | withCredentials: true 12 | }); 13 | return next.handle(request); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment-create/comment-create.component.html: -------------------------------------------------------------------------------- 1 |
2 | 13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/core/models/post.model.ts: -------------------------------------------------------------------------------- 1 | import { User } from './user.model'; 2 | import { Group } from './group.model'; 3 | import { Tag } from './tag.model'; 4 | import { Report } from './report.model'; 5 | 6 | export class Post { 7 | uuid: string; 8 | title: string; 9 | content: string; 10 | status: string; 11 | created_at: string; 12 | updated_at: string; 13 | author: User; 14 | votes: number; 15 | comments: number; 16 | user_vote: any; 17 | user_bookmark: any; 18 | tags: Tag[]; 19 | group: Group; 20 | report?: Report 21 | } 22 | -------------------------------------------------------------------------------- /reddit_clone/settings/development.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | 3 | DEBUG = True 4 | 5 | ALLOWED_HOSTS = ["localhost", "127.0.0.1"] 6 | 7 | # Development database (SQLite) 8 | DATABASES = { 9 | "default": { 10 | "ENGINE": "django.db.backends.sqlite3", 11 | "NAME": os.path.join(BASE_DIR, "db.sqlite3"), 12 | } 13 | } 14 | 15 | # Development email settings 16 | EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" 17 | 18 | # CORS settings for development 19 | CORS_ALLOWED_ORIGINS = [ 20 | "http://localhost:8000", 21 | "http://localhost:4200", 22 | ] 23 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-comments/profile-comments.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-profile-comments', 5 | templateUrl: './profile-comments.component.html', 6 | styleUrls: ['./profile-comments.component.scss'] 7 | }) 8 | export class ProfileCommentsComponent implements OnInit { 9 | @Input() comments: [] = []; 10 | @Input() user; 11 | @Input() self: boolean; 12 | @Input() currentUser: string; 13 | 14 | constructor() { } 15 | 16 | ngOnInit(): void { 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment-user/comment-user.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 10 | -------------------------------------------------------------------------------- /apps/core/models.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.db import models 4 | 5 | 6 | class TimeStampedModel(models.Model): 7 | """ 8 | An abstract base class model that provides self-updated ``created_at`` 9 | and ``updated_at`` fields 10 | """ 11 | 12 | created_at = models.DateTimeField(auto_now_add=True) 13 | updated_at = models.DateTimeField(auto_now=True) 14 | 15 | visitable = False 16 | 17 | class Meta: 18 | abstract = True 19 | 20 | def is_edited(self): 21 | return (self.updated_at - self.created_at).total_seconds() > 1 22 | 23 | edited = property(is_edited) 24 | -------------------------------------------------------------------------------- /apps/tags/migrations/0003_added_type_in_tag.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.14 on 2022-11-15 11:53 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('tags', '0002_added_tag_type'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='tag', 16 | name='tag_type', 17 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tags', to='tags.tagtype', verbose_name='tag type'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /apps/reports/models/user.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.db import models 3 | from reports.abstracts import AbstractReport 4 | 5 | 6 | class UserProfileReport(AbstractReport): 7 | reported_user = models.ForeignKey( 8 | User, on_delete=models.CASCADE, related_name="reports" 9 | ) 10 | 11 | class Meta: 12 | ordering = [ 13 | "-created_at", 14 | ] 15 | verbose_name = "User Profile Report" 16 | verbose_name_plural = "User Profile Reports" 17 | 18 | def __str__(self): 19 | return f"Report: {self.reported_user.username} by {self.reporter.username}" 20 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/group/group-search/group-search.component.scss: -------------------------------------------------------------------------------- 1 | .group-grid { 2 | display: flex; 3 | flex-wrap: wrap; 4 | gap: 16px; 5 | justify-content: center; 6 | } 7 | 8 | .group-item { 9 | // max-width: 700px; 10 | // width: 100%; 11 | max-width: 300px; 12 | width: 100%; 13 | min-height: 100px; 14 | box-shadow: none; 15 | border: 1px solid #dedede; 16 | border-radius: 8px; 17 | padding: 16px; 18 | display: flex; 19 | flex-direction: column; 20 | justify-content: space-between; 21 | 22 | // margin: 16px 0; 23 | .subtext { 24 | font-size: 0.75rem; 25 | color: #707070; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/bookmarks/models.py: -------------------------------------------------------------------------------- 1 | from bookmarks.abstracts import AbstractBookmark 2 | from django.db import models 3 | from posts.models import Post 4 | 5 | 6 | class PostBookmark(AbstractBookmark): 7 | post = models.ForeignKey( 8 | Post, verbose_name="post", on_delete=models.CASCADE, related_name="bookmarks" 9 | ) 10 | 11 | class Meta: 12 | ordering = [ 13 | "-created_at", 14 | ] 15 | unique_together = ["post", "user"] 16 | verbose_name = "Post Bookmark" 17 | verbose_name_plural = "Post Bookmarks" 18 | 19 | def __str__(self): 20 | return f"Bookmark: {self.post.title} by {self.user.username}" 21 | -------------------------------------------------------------------------------- /apps/posts/migrations/0002_altered_post_model.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.14 on 2022-11-17 15:06 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('groups', '0006_altered_group_model'), 11 | ('posts', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='post', 17 | name='group', 18 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='posts', to='groups.group'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /apps/profiles/serializers.py: -------------------------------------------------------------------------------- 1 | from core.serializers import ModelReadOnlySerializer 2 | from django.contrib.auth.models import User 3 | from rest_framework import serializers 4 | 5 | 6 | class UserSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = User 9 | fields = ( 10 | "id", 11 | "first_name", 12 | "last_name", 13 | "email", 14 | "username", 15 | "date_joined", 16 | ) 17 | 18 | 19 | class UserReadOnlySerializer(ModelReadOnlySerializer): 20 | class Meta: 21 | model = User 22 | fields = ("id", "first_name", "last_name", "email", "username") 23 | -------------------------------------------------------------------------------- /apps/comments/services.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.core.exceptions import ValidationError 3 | 4 | 5 | def add_mentioned_users(user_id, comment): 6 | try: 7 | user = User.objects.get(pk=user_id) 8 | except User.DoesNotExist: 9 | pass 10 | else: 11 | comment.mentioned_users.add(user) 12 | comment.save() 13 | return comment 14 | 15 | 16 | def remove_users(user_id, comment): 17 | try: 18 | user = User.objects.get(pk=user_id) 19 | except User.DoesNotExist: 20 | pass 21 | else: 22 | comment.mentioned_users.remove(user) 23 | comment.save() 24 | return comment 25 | -------------------------------------------------------------------------------- /apps/groups/serializers/invite.py: -------------------------------------------------------------------------------- 1 | from core.serializers import ModelReadOnlySerializer 2 | from groups.models import GroupInvite 3 | from rest_framework import serializers 4 | 5 | 6 | class GroupInviteReadOnlySerializer(ModelReadOnlySerializer): 7 | class Meta: 8 | model = GroupInvite 9 | fields = ("id", "group", "created_by", "user", "invite_as") 10 | 11 | 12 | class GroupInviteSerializer(serializers.ModelSerializer): 13 | class Meta: 14 | model = GroupInvite 15 | fields = ("id", "group", "created_by", "user", "invite_as") 16 | read_only_fields = ("id",) 17 | extra_kwargs = {"group": {"write_only": True}, "user": {"write_only": True}} 18 | -------------------------------------------------------------------------------- /apps/groups/router.py: -------------------------------------------------------------------------------- 1 | from groups.views import ( 2 | GroupInviteViewSet, 3 | GroupMemberViewSet, 4 | GroupRuleViewSet, 5 | GroupViewSet, 6 | MemberRequestViewSet, 7 | ) 8 | from rest_framework_nested import routers 9 | 10 | router = routers.SimpleRouter() 11 | router.register(r"groups", GroupViewSet) 12 | router.register(r"members", GroupMemberViewSet) 13 | 14 | group_router = routers.NestedSimpleRouter(router, r"groups", lookup="group") 15 | group_router.register(r"members", GroupMemberViewSet) 16 | group_router.register(r"member_requests", MemberRequestViewSet) 17 | group_router.register(r"invites", GroupInviteViewSet) 18 | group_router.register(r"rules", GroupRuleViewSet) 19 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment-user/comment-user.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { User } from '@reddit/core/models/user.model'; 3 | 4 | 5 | @Component({ 6 | selector: 'app-comment-user', 7 | templateUrl: './comment-user.component.html', 8 | styleUrls: ['./comment-user.component.scss'] 9 | }) 10 | export class CommentUserComponent implements OnInit { 11 | @Input() user: User; 12 | @Input() avatar_mode: boolean = false; 13 | @Input() header_mode: boolean = false; 14 | @Input() flair: string = 'User'; 15 | @Input() is_removed = false; 16 | 17 | constructor() { } 18 | 19 | ngOnInit() { 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/react-app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Route, BrowserRouter as Router, Routes } from "react-router-dom"; 2 | import SignIn from "./components/auth/SignIn/SignIn"; 3 | import SignUp from "./components/auth/SignUp/SignUp"; 4 | 5 | function App() { 6 | return ( 7 | 8 | 9 | {/* } /> */} 10 | } /> 11 | } /> 12 | {/* Optional: Redirect unknown paths to home */} 13 | {/* } /> */} 14 | 15 | 16 | ); 17 | } 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /apps/core/management/commands/populate_posts.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.core.management.base import BaseCommand 3 | from posts.models import Post 4 | 5 | from .posts import data 6 | 7 | 8 | class Command(BaseCommand): 9 | help = "Populate posts using placeholder data" 10 | 11 | def handle(self, *args, **kwargs): 12 | for user in User.objects.all(): 13 | for i in range(len(data)): 14 | new_data = data[i] 15 | post, created = Post.objects.get_or_create( 16 | title=new_data.get("title"), 17 | content=new_data.get("content"), 18 | author=user, 19 | ) 20 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/core/pipes/safe-content/safe-content.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { DomSanitizer } from '@angular/platform-browser'; 3 | @Pipe({ 4 | name: 'safeContent' 5 | }) 6 | export class SafeContentPipe implements PipeTransform { 7 | constructor(private sanitizer:DomSanitizer){} 8 | transform(content: string) { 9 | return this.sanitizer.bypassSecurityTrustHtml(content); 10 | // return this.sanitizer.bypassSecurityTrustStyle(html); 11 | // return this.sanitizer.bypassSecurityTrustScript(html); 12 | // return this.sanitizer.bypassSecurityTrustUrl(html); 13 | // return this.sanitizer.bypassSecurityTrustResourceUrl(html); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | from env_settings import configure_environment 7 | 8 | 9 | def main(): 10 | 11 | try: 12 | from django.core.management import execute_from_command_line 13 | except ImportError as exc: 14 | raise ImportError( 15 | "Couldn't import Django. Are you sure it's installed and " 16 | "available on your PYTHONPATH environment variable? Did you " 17 | "forget to activate a virtual environment?" 18 | ) from exc 19 | configure_environment() 20 | execute_from_command_line(sys.argv) 21 | 22 | 23 | if __name__ == "__main__": 24 | main() 25 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | SECRET_KEY=your_secret_key_here 2 | ENVIRONMENT=development 3 | 4 | # Email Settings 5 | HOME_EMAIL=admin@example.com 6 | DEFAULT_FROM_GMAIL=admin@example.com 7 | EMAIL_HOST=smtp.example.com 8 | EMAIL_HOST_USER=your_email@example.com 9 | EMAIL_HOST_PASSWORD=your_email_password 10 | EMAIL_PORT=587 11 | EMAIL_USE_TLS=True 12 | 13 | # Allowed Hosts (comma-separated for production) 14 | ALLOWED_HOSTS=localhost,127.0.0.1 15 | 16 | # CORS Allowed Origins (comma-separated for production) 17 | CORS_ALLOWED_ORIGINS=http://localhost:8000,http://localhost:4200 18 | 19 | # Database Settings (for PostgreSQL in production) 20 | DB_NAME=your_db_name 21 | DB_USER=your_db_user 22 | DB_PASSWORD=your_db_password 23 | DB_HOST=your_db_host 24 | DB_PORT=5432 25 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/core/models/comment.model.ts: -------------------------------------------------------------------------------- 1 | import { CommentService } from '@reddit/core/services/comment/comment.service'; 2 | import { User } from '@reddit/core/models/user.model'; 3 | 4 | 5 | export class Comment { 6 | id: number; 7 | user: User; 8 | mentioned_users: Array; 9 | comment: string; 10 | votes: number; 11 | edited: boolean; 12 | is_removed: boolean; 13 | flair: string; 14 | created_at: string; 15 | updated_at: string; 16 | parent: number; 17 | child_count: number; 18 | user_vote: { 19 | id: number; 20 | user: number; 21 | vote: number; 22 | } 23 | } 24 | 25 | export class UserComment { 26 | id: number; 27 | comment: string; 28 | created_at: string; 29 | } 30 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/react-app/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | import { defineConfig, globalIgnores } from 'eslint/config' 7 | 8 | export default defineConfig([ 9 | globalIgnores(['dist']), 10 | { 11 | files: ['**/*.{ts,tsx}'], 12 | extends: [ 13 | js.configs.recommended, 14 | tseslint.configs.recommended, 15 | reactHooks.configs.flat.recommended, 16 | reactRefresh.configs.vite, 17 | ], 18 | languageOptions: { 19 | ecmaVersion: 2020, 20 | globals: globals.browser, 21 | }, 22 | }, 23 | ]) 24 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/feed/feed.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FeedComponent } from './feed.component'; 4 | 5 | describe('FeedComponent', () => { 6 | let component: FeedComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ FeedComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(FeedComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/post/post/post.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PostComponent } from './post.component'; 4 | 5 | describe('PostComponent', () => { 6 | let component: PostComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ PostComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(PostComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /apps/posts/router.py: -------------------------------------------------------------------------------- 1 | from bookmarks.views import PostBookmarkViewSet 2 | from comments.views import PostCommentViewSet 3 | from followers.views import PostFollowerViewSet 4 | from posts.views import PostSelfViewSet, PostViewSet 5 | from reports.views import PostReportViewSet 6 | from rest_framework_nested import routers 7 | 8 | router = routers.SimpleRouter() 9 | router.register(r"posts", PostViewSet) 10 | router.register(r"post/self", PostSelfViewSet, basename="post-self") 11 | 12 | post_router = routers.NestedSimpleRouter(router, r"posts", lookup="post") 13 | post_router.register(r"comments", PostCommentViewSet) 14 | post_router.register(r"bookmarks", PostBookmarkViewSet) 15 | post_router.register(r"reports", PostReportViewSet) 16 | post_router.register(r"followers", PostFollowerViewSet) 17 | -------------------------------------------------------------------------------- /apps/tags/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Tag, TagType 4 | 5 | 6 | @admin.register(Tag) 7 | class TagAdmin(admin.ModelAdmin): 8 | list_display = ("id", "created_at", "updated_at", "name", "tag_type") 9 | list_filter = ("created_at", "updated_at", "tag_type") 10 | search_fields = ("name",) 11 | raw_id_fields = ("tag_type",) 12 | date_hierarchy = "created_at" 13 | 14 | 15 | @admin.register(TagType) 16 | class TagTypeAdmin(admin.ModelAdmin): 17 | list_display = ( 18 | "id", 19 | "created_at", 20 | "updated_at", 21 | "title", 22 | ) 23 | list_filter = ( 24 | "created_at", 25 | "updated_at", 26 | ) 27 | search_fields = ("title",) 28 | date_hierarchy = "created_at" 29 | -------------------------------------------------------------------------------- /apps/tags/serializers.py: -------------------------------------------------------------------------------- 1 | from core.serializers import ModelReadOnlySerializer 2 | from rest_framework import serializers 3 | 4 | from .models import Tag, TagType 5 | 6 | 7 | class TagTypeSerializer(serializers.ModelSerializer): 8 | class Meta: 9 | model = TagType 10 | fields = ( 11 | "id", 12 | "title", 13 | ) 14 | read_only_fields = ("id",) 15 | 16 | 17 | class TagSerializer(serializers.ModelSerializer): 18 | tag_type = TagTypeSerializer() 19 | 20 | class Meta: 21 | model = Tag 22 | fields = ("id", "name", "tag_type") 23 | read_only_fields = ("id",) 24 | 25 | 26 | class TagReadOnlySerializer(ModelReadOnlySerializer): 27 | class Meta: 28 | model = Tag 29 | fields = "__all__" 30 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('reddit-app app is running!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/group/group/group.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { GroupComponent } from './group.component'; 4 | 5 | describe('GroupComponent', () => { 6 | let component: GroupComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ GroupComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(GroupComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./src", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "downlevelIteration": true, 10 | "experimentalDecorators": true, 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "module": "es2020", 15 | "lib": [ 16 | "es2018", 17 | "dom" 18 | ], 19 | "paths": { 20 | "@reddit/app/*": ["src/app/*"], 21 | "@reddit/core/*": ["src/app/core/*"], 22 | "@reddit/env/*": ["src/environments/*"], 23 | "@reddit/assets/*": ["src/assets/*"], 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-downvotes/profile-downvotes.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | {{ item.created_at | timeSince }} ago 10 | {{ item.post_comment.comment }} 11 |
12 |
13 |
14 |
15 | 16 | 17 |

No content to show

18 |
19 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/search/search.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SearchComponent } from './search.component'; 4 | 5 | describe('SearchComponent', () => { 6 | let component: SearchComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ SearchComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SearchComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/auth/sign-in/sign-in.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SignInComponent } from './sign-in.component'; 4 | 5 | describe('SignInComponent', () => { 6 | let component: SignInComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ SignInComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SignInComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/auth/sign-up/sign-up.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SignUpComponent } from './sign-up.component'; 4 | 5 | describe('SignUpComponent', () => { 6 | let component: SignUpComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ SignUpComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SignUpComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-upvotes/profile-upvotes.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | {{ item.created_at | timeSince }} ago 10 | {{ item.post_comment.comment }} 11 |
12 |
13 |
14 |
15 | 16 | 17 |

No content to show

18 |
19 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/auth/sign-out/sign-out.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SignOutComponent } from './sign-out.component'; 4 | 5 | describe('SignOutComponent', () => { 6 | let component: SignOutComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ SignOutComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SignOutComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/react-app/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2023", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "types": ["node"], 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "verbatimModuleSyntax": true, 14 | "moduleDetection": "force", 15 | "noEmit": true, 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "erasableSyntaxOnly": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "noUncheckedSideEffectImports": true 24 | }, 25 | "include": ["vite.config.ts"] 26 | } 27 | -------------------------------------------------------------------------------- /apps/groups/models/member_request.py: -------------------------------------------------------------------------------- 1 | from core.models import TimeStampedModel 2 | from django.contrib.auth.models import User 3 | from django.db import models 4 | from groups.models import Group 5 | 6 | 7 | class MemberRequest(TimeStampedModel): 8 | group = models.ForeignKey( 9 | Group, related_name="member_requests", on_delete=models.CASCADE 10 | ) 11 | user = models.ForeignKey( 12 | User, related_name="requested_groups", on_delete=models.CASCADE 13 | ) 14 | is_approved = models.BooleanField(default=False) 15 | 16 | class Meta: 17 | ordering = ["-created_at"] 18 | verbose_name = "Member Request" 19 | verbose_name_plural = "Member Requests" 20 | 21 | def __str__(self): 22 | return f"{self.user.username} sent a request to join {self.group.name}" 23 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment/comment.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CommentComponent } from './comment.component'; 4 | 5 | describe('CommentComponent', () => { 6 | let component: CommentComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ CommentComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CommentComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profiles/profiles.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProfileComponent } from './profiles.component'; 4 | 5 | describe('ProfileComponent', () => { 6 | let component: ProfileComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ProfileComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ProfileComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/setttings/setttings.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SetttingsComponent } from './setttings.component'; 4 | 5 | describe('SetttingsComponent', () => { 6 | let component: SetttingsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ SetttingsComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SetttingsComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /apps/groups/serializers/member.py: -------------------------------------------------------------------------------- 1 | from core.serializers import ModelReadOnlySerializer 2 | from groups.models import GroupMember 3 | from groups.serializers import GroupReadOnlyLightSerializer 4 | from profiles.serializers import UserReadOnlySerializer 5 | from rest_framework import serializers 6 | 7 | 8 | class GroupMemberReadOnlySerializer(ModelReadOnlySerializer): 9 | group = GroupReadOnlyLightSerializer() 10 | 11 | class Meta: 12 | model = GroupMember 13 | fields = ("id", "group", "user", "member_type", "status") 14 | 15 | 16 | class GroupMemberSerializer(serializers.ModelSerializer): 17 | group = GroupReadOnlyLightSerializer() 18 | user = UserReadOnlySerializer() 19 | 20 | class Meta: 21 | model = GroupMember 22 | fields = ("id", "group", "user", "member_type", "status") 23 | -------------------------------------------------------------------------------- /apps/groups/signals.py: -------------------------------------------------------------------------------- 1 | from django.db.models.signals import post_save, pre_save 2 | from django.dispatch import receiver 3 | from groups.models import Group, GroupMember, MemberRequest 4 | from groups.services import assign_permissions 5 | 6 | 7 | @receiver(post_save, sender=MemberRequest) 8 | def member_request_created_hook(sender, instance, created, **kwargs): 9 | if created and instance: 10 | if instance.group.group_type == "PUBLIC": 11 | member, created = GroupMember.objects.create( 12 | group=instance.group, user=instance.user 13 | ) 14 | 15 | 16 | @receiver(pre_save, sender=GroupMember) 17 | def permissions_for_member_type(sender, instance, created, **kwargs): 18 | if created.id: 19 | assign_permissions(instance.member_type, instance.member, instance.group) 20 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/components/report-dialog/report-dialog.component.html: -------------------------------------------------------------------------------- 1 |

Submit a report

2 | 3 | 4 | 5 | {{ type.title }} 6 | 7 | 8 | 9 |
10 |

{{ selectedType.info }}

11 |
12 | 13 |
14 | 15 | 16 |
17 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/group/group-feed/group-feed.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { GroupFeedComponent } from './group-feed.component'; 4 | 5 | describe('GroupFeedComponent', () => { 6 | let component: GroupFeedComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ GroupFeedComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(GroupFeedComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/group/group-post/group-post.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { GroupPostComponent } from './group-post.component'; 4 | 5 | describe('GroupPostComponent', () => { 6 | let component: GroupPostComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ GroupPostComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(GroupPostComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/react-app/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/auth.header.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpXsrfTokenExtractor } from '@angular/common/http'; 3 | import { Injectable } from '@angular/core'; 4 | 5 | @Injectable() 6 | export class HttpXsrfInterceptor implements HttpInterceptor { 7 | 8 | constructor( 9 | private tokenExtractor: HttpXsrfTokenExtractor, 10 | ) { } 11 | 12 | intercept(req: HttpRequest, next: HttpHandler): Observable> { 13 | const headerName = 'X-CSRFToken'; 14 | const token = this.tokenExtractor.getToken() as string; 15 | if (token !== null && !req.headers.has(headerName)) { 16 | req = req.clone({ headers: req.headers.set(headerName, token) }); 17 | } 18 | return next.handle(req); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/post/create-post/create-post.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CreatePostComponent } from './create-post.component'; 4 | 5 | describe('CreatePostComponent', () => { 6 | let component: CreatePostComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ CreatePostComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CreatePostComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/post/post-detail/post-detail.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PostDetailComponent } from './post-detail.component'; 4 | 5 | describe('PostDetailComponent', () => { 6 | let component: PostDetailComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ PostDetailComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(PostDetailComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/post/post-loader/post-loader.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PostLoaderComponent } from './post-loader.component'; 4 | 5 | describe('PostLoaderComponent', () => { 6 | let component: PostLoaderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ PostLoaderComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(PostLoaderComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment-edit/comment-edit.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CommentEditComponent } from './comment-edit.component'; 4 | 5 | describe('CommentEditComponent', () => { 6 | let component: CommentEditComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ CommentEditComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CommentEditComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment-list/comment-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CommentListComponent } from './comment-list.component'; 4 | 5 | describe('CommentListComponent', () => { 6 | let component: CommentListComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ CommentListComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CommentListComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment-user/comment-user.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CommentUserComponent } from './comment-user.component'; 4 | 5 | describe('CommentUserComponent', () => { 6 | let component: CommentUserComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ CommentUserComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CommentUserComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/group/create-group/create-group.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CreateGroupComponent } from './create-group.component'; 4 | 5 | describe('CreateGroupComponent', () => { 6 | let component: CreateGroupComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ CreateGroupComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CreateGroupComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/group/group-router/group-router.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { GroupRouterComponent } from './group-router.component'; 4 | 5 | describe('GroupRouterComponent', () => { 6 | let component: GroupRouterComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ GroupRouterComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(GroupRouterComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/group/group-search/group-search.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { GroupSearchComponent } from './group-search.component'; 4 | 5 | describe('GroupSearchComponent', () => { 6 | let component: GroupSearchComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ GroupSearchComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(GroupSearchComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment/comment.component.scss: -------------------------------------------------------------------------------- 1 | .user-avatar { 2 | height: 40px; 3 | width: 40px; 4 | } 5 | 6 | .grey-700 { 7 | color: #707070; 8 | } 9 | 10 | .font-xxs { 11 | font-size: 0.75rem; 12 | } 13 | 14 | @media (max-width: 640px) { 15 | .col-user { 16 | max-width: 15%!important; 17 | } 18 | } 19 | 20 | .comment-text { 21 | font-size: 14px; 22 | line-height: 22px; 23 | white-space: inherit; 24 | margin:0; 25 | margin-bottom: 12px; 26 | } 27 | 28 | ::ng-deep { 29 | .mat-menu-item { 30 | line-height: 30px; 31 | padding : 0 12px ; 32 | height : 40px; 33 | } 34 | 35 | .mat-menu-content:not(:empty) { 36 | padding-top : 0; 37 | padding-bottom: 0; 38 | } 39 | 40 | .mat-menu-panel { 41 | border-radius: 8px; 42 | min-height: inherit; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment-group/comment-group.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CommentGroupComponent } from './comment-group.component'; 4 | 5 | describe('CommentGroupComponent', () => { 6 | let component: CommentGroupComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ CommentGroupComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CommentGroupComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-posts/profile-posts.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProfilePostsComponent } from './profile-posts.component'; 4 | 5 | describe('ProfilePostsComponent', () => { 6 | let component: ProfilePostsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ProfilePostsComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ProfilePostsComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /apps/groups/services.py: -------------------------------------------------------------------------------- 1 | from groups.models import Group, GroupMember 2 | from guardian.shortcuts import assign_perm, remove_perm 3 | 4 | 5 | def assign_permissions(member_type, user, group): 6 | if member_type == GroupMember.MemberTypes.MEMBER.value: 7 | return 8 | elif member_type == GroupMember.MemberTypes.MODERATOR.value: 9 | assign_perm("add_members", user, group) 10 | assign_perm("edit_groups", user, group) 11 | elif member_type == GroupMember.MemberTypes.ADMIN.value: 12 | assign_permissions(GroupMember.MemberTypes.MODERATOR.value, user, group) 13 | assign_perm("add_moderators", user, group) 14 | assign_perm("remove_moderators", user, group) 15 | assign_perm("change_members", user, group) 16 | assign_perm("delete_members", user, group) 17 | assign_perm("delete_groups", user, group) 18 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/components/report-dialog/report-dialog.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ReportDialogComponent } from './report-dialog.component'; 4 | 5 | describe('ReportDialogComponent', () => { 6 | let component: ReportDialogComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ReportDialogComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ReportDialogComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment-create/comment-create.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CommentCreateComponent } from './comment-create.component'; 4 | 5 | describe('CommentCreateComponent', () => { 6 | let component: CommentCreateComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ CommentCreateComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CommentCreateComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment-footer/comment-footer.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CommentFooterComponent } from './comment-footer.component'; 4 | 5 | describe('CommentFooterComponent', () => { 6 | let component: CommentFooterComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ CommentFooterComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CommentFooterComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-history/profile-history.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProfileHistoryComponent } from './profile-history.component'; 4 | 5 | describe('ProfileHistoryComponent', () => { 6 | let component: ProfileHistoryComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ProfileHistoryComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ProfileHistoryComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-upvotes/profile-upvotes.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProfileUpvotesComponent } from './profile-upvotes.component'; 4 | 5 | describe('ProfileUpvotesComponent', () => { 6 | let component: ProfileUpvotesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ProfileUpvotesComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ProfileUpvotesComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/group/group-feed/group-feed.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /apps/tags/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.14 on 2022-09-10 10:51 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Tag', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('created_at', models.DateTimeField(auto_now_add=True)), 19 | ('updated_at', models.DateTimeField(auto_now=True)), 20 | ('name', models.CharField(max_length=30)), 21 | ], 22 | options={ 23 | 'verbose_name': 'Tag', 24 | 'verbose_name_plural': 'Tags', 25 | }, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /apps/tags/models.py: -------------------------------------------------------------------------------- 1 | from core.models import TimeStampedModel 2 | from django.db import models 3 | from django.utils.translation import gettext_lazy as _ 4 | 5 | 6 | class TagType(TimeStampedModel): 7 | title = models.CharField(max_length=15) 8 | 9 | class Meta: 10 | verbose_name = "Tag Type" 11 | verbose_name_plural = "Tag Types" 12 | 13 | def __str__(self): 14 | return f"{self.title}" 15 | 16 | 17 | class Tag(TimeStampedModel): 18 | name = models.CharField(max_length=30) 19 | tag_type = models.ForeignKey( 20 | TagType, 21 | null=True, 22 | verbose_name=_("tag type"), 23 | related_name="tags", 24 | on_delete=models.SET_NULL, 25 | ) 26 | 27 | class Meta: 28 | verbose_name = "Tag" 29 | verbose_name_plural = "Tags" 30 | 31 | def __str__(self): 32 | return f"{self.name}" 33 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/components/confirmation-dialog/confirmation-dialog.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject, OnInit } from '@angular/core'; 2 | import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; 3 | 4 | @Component({ 5 | selector: 'app-confirmation-dialog', 6 | templateUrl: './confirmation-dialog.component.html', 7 | styleUrls: ['./confirmation-dialog.component.scss'] 8 | }) 9 | export class ConfirmationDialogComponent implements OnInit { 10 | showCancel: boolean; 11 | 12 | constructor( 13 | public dialogRef: MatDialogRef, 14 | @Inject(MAT_DIALOG_DATA) public data: any, 15 | ) { } 16 | 17 | ngOnInit() { 18 | this.showCancel = this.data.showCancel ? this.data.showCancel : true; 19 | } 20 | 21 | closeDialog(isConfirmed: boolean) { 22 | this.dialogRef.close(isConfirmed); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-comments/profile-comments.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProfileCommentsComponent } from './profile-comments.component'; 4 | 5 | describe('ProfileCommentsComponent', () => { 6 | let component: ProfileCommentsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ProfileCommentsComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ProfileCommentsComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-overview/profile-overview.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProfileOverviewComponent } from './profile-overview.component'; 4 | 5 | describe('ProfileOverviewComponent', () => { 6 | let component: ProfileOverviewComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ProfileOverviewComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ProfileOverviewComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /.github/workflows/django.yml: -------------------------------------------------------------------------------- 1 | name: Django CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | strategy: 14 | max-parallel: 4 15 | matrix: 16 | python-version: [3.8.16, 3.9.16] 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Set up Python ${{ matrix.python-version }} 21 | id: setup-python 22 | uses: actions/setup-python@v3 23 | with: 24 | python-version: ${{ matrix.python-version }} 25 | - name: Install Dependencies 26 | run: | 27 | python -m pip install --upgrade pip 28 | pip install -r requirements.txt 29 | - name: Save Setup-Python Name 30 | run: echo "SETUP_PYTHON_${{ matrix.python-version }}_NAME=${{ steps.setup-python.outputs.python-version }}" >> $GITHUB_STATE 31 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment-user/comment-user.component.scss: -------------------------------------------------------------------------------- 1 | .user-avatar { 2 | height : 30px; 3 | width : 30px; 4 | border-radius: 20px; 5 | border : 1px solid #eee; 6 | object-fit : cover; 7 | background: grey; 8 | } 9 | 10 | .flair-badge { 11 | border: 2px solid #5CCF22; 12 | border-radius: 4px; 13 | padding: 1px 3px; 14 | margin-left: 6px; 15 | } 16 | 17 | .comment-text { 18 | font-size: 16px; 19 | line-height: 18.75px; 20 | 21 | @media only screen and (max-width: 960px) { 22 | font-size: 14px; 23 | line-height: 16.41px; 24 | } 25 | } 26 | .comment-subtext { 27 | font-size: 14px; 28 | line-height: 16px; 29 | white-space: nowrap; 30 | overflow: hidden; 31 | text-overflow: ellipsis; 32 | @media only screen and (max-width: 960px) { 33 | font-size: 12px; 34 | line-height: 14px; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-bookmarks/profile-bookmarks.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProfileBookmarksComponent } from './profile-bookmarks.component'; 4 | 5 | describe('ProfileBookmarksComponent', () => { 6 | let component: ProfileBookmarksComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ProfileBookmarksComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ProfileBookmarksComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-downvotes/profile-downvotes.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProfileDownvotesComponent } from './profile-downvotes.component'; 4 | 5 | describe('ProfileDownvotesComponent', () => { 6 | let component: ProfileDownvotesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ProfileDownvotesComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ProfileDownvotesComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | keys(): string[]; 13 | (id: string): T; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting() 21 | ); 22 | // Then we find all the tests. 23 | const context = require.context('./', true, /\.spec\.ts$/); 24 | // And load the modules. 25 | context.keys().map(context); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/react-app/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 | "target": "ES2022", 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2022", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "types": ["vite/client"], 9 | "skipLibCheck": true, 10 | 11 | /* Bundler mode */ 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "moduleDetection": "force", 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | 19 | /* Linting */ 20 | "strict": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "erasableSyntaxOnly": true, 24 | "noFallthroughCasesInSwitch": true, 25 | "noUncheckedSideEffectImports": true 26 | }, 27 | "include": ["src"] 28 | } 29 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment-footer/comment-footer.component.scss: -------------------------------------------------------------------------------- 1 | .button-active { 2 | color: #DC281E!important; 3 | border: 1px solid #DC281E!important; 4 | border-radius: 50%; 5 | } 6 | 7 | .button-inactive { 8 | color : #545454; 9 | border: 1px solid #d3d3d3; 10 | border-radius: 50%; 11 | } 12 | 13 | .reply-btn { 14 | text-decoration: none; 15 | margin-left : 8px; 16 | border : 1px solid #d3d3d3; 17 | padding : 6px 12px; 18 | border-radius : 4px; 19 | font-size: 12px; 20 | line-height: 16.41px; 21 | color: #232323; 22 | font-weight: 400; 23 | cursor : pointer; 24 | } 25 | 26 | .mat-icon-button { 27 | line-height: 30px; 28 | width: 30px; 29 | height: 30px; 30 | } 31 | 32 | .border { 33 | border: 1px solid #dedede; 34 | } 35 | 36 | .comment-votes { 37 | // padding: 0 10px; 38 | margin: 8px 0; 39 | } 40 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/core/services/report/report.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { environment } from '@reddit/env/environment'; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export class ReportService { 9 | private baseUrl = `${environment.serverUrl}${environment.baseUrl}`; 10 | 11 | constructor(private http: HttpClient) { 12 | } 13 | 14 | getReportTypes() { 15 | return this.http.get(this.baseUrl + 'report_types/'); 16 | } 17 | 18 | createReport(post_uuid: string, report) { 19 | return this.http.post(this.baseUrl + 'posts/' + post_uuid + '/reports/', report); 20 | } 21 | 22 | redactReport(post_uuid: string, report_id: number) { 23 | return this.http.put(this.baseUrl + 'posts/' + post_uuid + '/reports/' + report_id + '/redact/', {}); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /apps/core/management/commands/populate_tags.py: -------------------------------------------------------------------------------- 1 | import csv 2 | from pathlib import Path 3 | 4 | from django.core.management.base import BaseCommand, CommandError 5 | from tags.models import Tag, TagType 6 | 7 | 8 | class Command(BaseCommand): 9 | help = "Populate Tag and TagType from the CSV provided" 10 | 11 | def handle(self, *args, **options): 12 | for type in ["INTEREST", "GENERAL", "TOPIC"]: 13 | TagType.objects.create(title=type) 14 | 15 | path = Path("tags.csv").absolute() 16 | with open(path) as csvfile: 17 | reader = csv.DictReader(csvfile) 18 | for row in reader: 19 | name = row["name"].title() 20 | type = row[" type"].lstrip().rstrip().upper() 21 | tag_type = TagType.objects.get(title=type) 22 | tag, created = Tag.objects.get_or_create(name=name, tag_type=tag_type) 23 | -------------------------------------------------------------------------------- /apps/followers/serializers.py: -------------------------------------------------------------------------------- 1 | from followers.models import PostFollower, UserFollower 2 | from rest_framework import serializers 3 | 4 | 5 | class PostFollowerSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = PostFollower 8 | fields = ("id", "post", "follower") 9 | read_only_fields = ("id",) 10 | extra_kwargs = {"post": {"write_only": True}} 11 | 12 | def create(self, validated_data): 13 | return PostFollower.objects.create(**validated_data) 14 | 15 | 16 | class UserFollowerSerializer(serializers.ModelSerializer): 17 | class Meta: 18 | model = UserFollower 19 | fields = ("id", "followed_user", "follower") 20 | read_only_fields = ("id",) 21 | extra_kwargs = {"followed_user": {"write_only": True}} 22 | 23 | def create(self, validated_data): 24 | return UserFollower.objects.create(**validated_data) 25 | -------------------------------------------------------------------------------- /apps/reports/serializers/post.py: -------------------------------------------------------------------------------- 1 | from reports.models import PostReport 2 | from rest_framework import serializers 3 | 4 | 5 | class PostReportSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = PostReport 8 | fields = ( 9 | "id", 10 | "reporter", 11 | "report_type", 12 | "url", 13 | "post", 14 | "additional_info", 15 | "status", 16 | ) 17 | read_only_fields = ("id",) 18 | extra_kwargs = {"post": {"write_only": True}} 19 | 20 | def create(self, validated_data): 21 | report = PostReport.objects.create(**validated_data) 22 | return report 23 | 24 | 25 | class PostReportLightSerializer(serializers.ModelSerializer): 26 | class Meta: 27 | model = PostReport 28 | fields = ("id", "status") 29 | read_only_fields = ("id", "status") 30 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/add-interest-dialog/add-interest-dialog.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AddInterestDialogComponent } from './add-interest-dialog.component'; 4 | 5 | describe('AddInterestDialogComponent', () => { 6 | let component: AddInterestDialogComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ AddInterestDialogComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AddInterestDialogComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/components/confirmation-dialog/confirmation-dialog.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ConfirmationDialogComponent } from './confirmation-dialog.component'; 4 | 5 | describe('ConfirmationDialogComponent', () => { 6 | let component: ConfirmationDialogComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ConfirmationDialogComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ConfirmationDialogComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /apps/tags/migrations/0002_added_tag_type.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.14 on 2022-11-15 11:52 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('tags', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='TagType', 15 | fields=[ 16 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 17 | ('created_at', models.DateTimeField(auto_now_add=True)), 18 | ('updated_at', models.DateTimeField(auto_now=True)), 19 | ('title', models.CharField(max_length=15)), 20 | ], 21 | options={ 22 | 'verbose_name': 'Tag Type', 23 | 'verbose_name_plural': 'Tag Types', 24 | }, 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/search/search.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 | 15 | 16 |
17 | 18 |
19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /apps/groups/models/rules.py: -------------------------------------------------------------------------------- 1 | from core.models import TimeStampedModel 2 | from django.contrib.auth.models import User 3 | from django.db import models 4 | from groups.models import Group 5 | 6 | 7 | class GroupRule(TimeStampedModel): 8 | class RuleType(models.TextChoices): 9 | POSTS = "POSTS" 10 | COMMENTS = "COMMENTS" 11 | BOTH = "BOTH" 12 | 13 | group = models.ForeignKey(Group, related_name="rules", on_delete=models.CASCADE) 14 | title = models.CharField(max_length=100) 15 | description = models.CharField(max_length=500) 16 | rule_type = models.CharField( 17 | choices=RuleType.choices, max_length=10, default=RuleType.BOTH 18 | ) 19 | 20 | class Meta: 21 | ordering = ["-created_at"] 22 | verbose_name = "Group Rule" 23 | verbose_name_plural = "Group Rules" 24 | 25 | def __str__(self): 26 | return f"{self.group.name} rule : {self.title[:40]}" 27 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.8.1 2 | certifi==2024.7.4 3 | cffi==1.16.0 4 | charset-normalizer==3.3.2 5 | cryptography==42.0.8 6 | defusedxml==0.7.1 7 | dj-rest-auth==7.0.1 8 | Django==4.2.26 9 | django-allauth==0.63.0 10 | django-ckeditor==6.1.0 11 | django-cors-headers==4.3.1 12 | django-filter==24.2 13 | django-guardian==3.0.0 14 | django-js-asset==3.1.2 15 | djangorestframework==3.15.2 16 | drf-nested-routers==0.93.5 17 | drf-yasg==1.21.8 18 | gunicorn==22.0.0 19 | idna==3.7 20 | inflection==0.5.1 21 | Jinja2==3.1.6 22 | MarkupSafe==3.0.3 23 | oauthlib==3.2.2 24 | packaging==24.2 25 | pillow==10.4.0 26 | psycopg2-binary==2.9.9 27 | pycparser==2.22 28 | PyJWT==2.8.0 29 | python-dotenv==1.0.1 30 | python3-openid==3.2.0 31 | pytz==2024.1 32 | PyYAML==6.0.3 33 | requests==2.32.3 34 | requests-oauthlib==1.3.1 35 | six==1.16.0 36 | sqlparse==0.5.0 37 | typing_extensions==4.15.0 38 | uritemplate==4.1.1 39 | urllib3==2.2.1 40 | whitenoise==6.11.0 41 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line. 18 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 19 | -------------------------------------------------------------------------------- /apps/reports/serializers/user.py: -------------------------------------------------------------------------------- 1 | from reports.models import UserProfileReport 2 | from rest_framework import serializers 3 | 4 | 5 | class UserProfileReportSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = UserProfileReport 8 | fields = ( 9 | "id", 10 | "reporter", 11 | "report_type", 12 | "url", 13 | "reported_user", 14 | "additional_info", 15 | "status", 16 | ) 17 | read_only_fields = ("id",) 18 | extra_kwargs = {"reported_user": {"write_only": True}} 19 | 20 | def create(self, validated_data): 21 | report = UserProfileReport.objects.create(**validated_data) 22 | return report 23 | 24 | 25 | class UserProfileReportLightSerializer(serializers.ModelSerializer): 26 | class Meta: 27 | model = UserProfileReport 28 | fields = ("id", "status") 29 | read_only_fields = ("id", "status") 30 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/react-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-app", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "react": "^19.2.0", 14 | "react-dom": "^19.2.0", 15 | "react-router-dom": "^7.9.6" 16 | }, 17 | "devDependencies": { 18 | "@eslint/js": "^9.39.1", 19 | "@types/node": "^24.10.1", 20 | "@types/react": "^19.2.5", 21 | "@types/react-dom": "^19.2.3", 22 | "@vitejs/plugin-react": "^5.1.1", 23 | "babel-plugin-react-compiler": "^1.0.0", 24 | "eslint": "^9.39.1", 25 | "eslint-plugin-react-hooks": "^7.0.1", 26 | "eslint-plugin-react-refresh": "^0.4.24", 27 | "globals": "^16.5.0", 28 | "typescript": "~5.9.3", 29 | "typescript-eslint": "^8.46.4", 30 | "vite": "^7.2.4" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/post/create-post/create-post.component.scss: -------------------------------------------------------------------------------- 1 | .post-title { 2 | font-weight: 500; 3 | font-size: 20px; 4 | width: 100%; 5 | 6 | ::placeholder { 7 | font-size: 14px; 8 | font-weight: 400; 9 | } 10 | } 11 | 12 | .blog-form { 13 | margin-bottom: 32px; 14 | } 15 | 16 | .post-create { 17 | margin: 16px 0; 18 | box-shadow: none; 19 | border: 1px solid #dedede; 20 | border-radius: 8px; 21 | display: flex; 22 | padding: 20px; 23 | flex-direction: column; 24 | } 25 | 26 | .create-post-subtext { 27 | font-size: 14px; 28 | color: #707070; 29 | 30 | b { 31 | font-weight: 500; 32 | color: #232323; 33 | } 34 | } 35 | 36 | .community-icon { 37 | height: 40px; 38 | width: 40px; 39 | border: 1px solid #e6e6e6; 40 | border-radius: 50%; 41 | padding: 2px; 42 | object-fit: cover; 43 | } 44 | 45 | ::ng-deep { 46 | .fr-view { 47 | font-family: 'Roboto', serif!important; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /apps/reports/migrations/0001_report_type_model.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.14 on 2022-11-23 07:56 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='ReportType', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('created_at', models.DateTimeField(auto_now_add=True)), 19 | ('updated_at', models.DateTimeField(auto_now=True)), 20 | ('title', models.TextField(max_length=200)), 21 | ('info', models.TextField(blank=True)), 22 | ], 23 | options={ 24 | 'verbose_name': 'Report Type', 25 | 'verbose_name_plural': 'Report Types', 26 | }, 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /apps/followers/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from followers.models import PostFollower, UserFollower 3 | 4 | 5 | @admin.register(PostFollower) 6 | class PostFollowerAdmin(admin.ModelAdmin): 7 | list_display = ( 8 | "id", 9 | "created_at", 10 | "post", 11 | "follower", 12 | ) 13 | list_filter = ( 14 | "created_at", 15 | "updated_at", 16 | "post", 17 | "follower", 18 | ) 19 | raw_id_fields = ("follower", "post") 20 | date_hierarchy = "created_at" 21 | 22 | 23 | @admin.register(UserFollower) 24 | class UserFollowerAdmin(admin.ModelAdmin): 25 | list_display = ( 26 | "id", 27 | "created_at", 28 | "followed_user", 29 | "follower", 30 | ) 31 | list_filter = ( 32 | "created_at", 33 | "updated_at", 34 | "followed_user", 35 | "follower", 36 | ) 37 | raw_id_fields = ("follower", "followed_user") 38 | date_hierarchy = "created_at" 39 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Django Reddit 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /apps/profiles/models.py: -------------------------------------------------------------------------------- 1 | from core.models import TimeStampedModel 2 | from django.contrib.auth.models import User 3 | from django.db import models 4 | from tags.models import Tag 5 | 6 | 7 | class UserMetaInfo(TimeStampedModel): 8 | user = models.OneToOneField( 9 | User, verbose_name="user", related_name="meta_info", on_delete=models.CASCADE 10 | ) 11 | username_changed = models.DateField( 12 | null=True, 13 | blank=True, 14 | verbose_name="username_changed", 15 | ) 16 | bio = models.TextField(blank=True) 17 | dob = models.DateField(null=True) 18 | dob_visible = models.BooleanField(default=False) 19 | is_admin = models.BooleanField(default=False) 20 | is_banned = models.BooleanField(default=False) 21 | is_requesting_delete = models.BooleanField(default=False) 22 | 23 | class Meta: 24 | verbose_name = "User Meta Info" 25 | verbose_name_plural = "User Meta Info" 26 | 27 | def __str__(self): 28 | return f"Profile Info: {self.username}" 29 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ 31 | spec: { 32 | displayStacktrace: StacktraceOption.PRETTY 33 | } 34 | })); 35 | } 36 | }; -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/post/post-detail/post-detail.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

{{ post?.title }}

9 | 10 |

11 | 12 | more_horiz 13 | 14 |
15 |

16 | Comments 17 |

18 | 19 | 24 | 25 |
26 |
27 |
28 | -------------------------------------------------------------------------------- /apps/groups/models/invite.py: -------------------------------------------------------------------------------- 1 | from core.models import TimeStampedModel 2 | from django.contrib.auth.models import User 3 | from django.db import models 4 | from groups.models import Group 5 | 6 | 7 | class GroupInvite(TimeStampedModel): 8 | class InviteAs(models.TextChoices): 9 | MEMBER = "MEMBER" 10 | MODERATOR = "MODERATOR" 11 | 12 | group = models.ForeignKey(Group, related_name="invites", on_delete=models.CASCADE) 13 | created_by = models.ForeignKey( 14 | User, related_name="invitations", on_delete=models.CASCADE 15 | ) 16 | user = models.ForeignKey(User, related_name="invites", on_delete=models.CASCADE) 17 | invite_as = models.CharField( 18 | choices=InviteAs.choices, default=InviteAs.MEMBER, max_length=10 19 | ) 20 | 21 | class Meta: 22 | ordering = ["-created_at"] 23 | verbose_name = "Group Invite" 24 | verbose_name_plural = "Group Invites" 25 | 26 | def __str__(self): 27 | return f"{self.created_by.username} has invited {self.user.username} \ 28 | to join {self.group.name} as a {self.invite_as}." 29 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/components/confirmation-dialog/confirmation-dialog.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

5 | close 6 |
7 | 8 |
9 |
10 |

11 | {{ data.message }} 12 |

13 |
14 | 15 |
16 | 17 | 18 |
19 |
20 |
21 | 22 | 23 | 24 |
25 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-posts/profile-posts.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 |
12 |
13 | 14 | 15 |
16 | 17 |
18 |
19 |
20 |
21 | 22 | 23 |

{{ currentUser }} has not addded any posts yet

24 |
25 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment-footer/comment-footer.component.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 |

{{ comment.votes }}

7 | 8 | 11 | 12 | 21 |
22 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/README.md: -------------------------------------------------------------------------------- 1 | # RedditApp 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.2.4. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 28 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/reddit-app'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /apps/core/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import mixins, status, viewsets 2 | from rest_framework.response import Response 3 | 4 | from .mixins import ( 5 | DestroyModelMixin, 6 | MultiPermissionViewSetMixin, 7 | MultiSerializerViewSetMixin, 8 | PaginatedResponseMixin, 9 | ) 10 | 11 | 12 | class BaseReadOnlyViewSet( 13 | mixins.ListModelMixin, 14 | mixins.RetrieveModelMixin, 15 | MultiSerializerViewSetMixin, 16 | MultiPermissionViewSetMixin, 17 | PaginatedResponseMixin, 18 | viewsets.GenericViewSet, 19 | ): 20 | 21 | def get_serializer_context(self): 22 | context = super(BaseReadOnlyViewSet, self).get_serializer_context() 23 | context.update({"request": self.request}) 24 | return context 25 | 26 | def retrieve(self, request, *args, **kwargs): 27 | obj = self.get_object() 28 | serializer_class = self.get_serializer_class() 29 | serializer = serializer_class(obj) 30 | return Response(serializer.data, status=status.HTTP_200_OK) 31 | 32 | 33 | class BaseViewSet( 34 | mixins.CreateModelMixin, 35 | mixins.UpdateModelMixin, 36 | DestroyModelMixin, 37 | BaseReadOnlyViewSet, 38 | ): 39 | pass 40 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/core/guards/auth/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router'; 3 | import { Observable } from 'rxjs'; 4 | 5 | import { environment } from '@reddit/env/environment'; 6 | import { UserService } from '@reddit/core/services/user/user.service'; 7 | import { User } from '@reddit/core/models/user.model'; 8 | 9 | @Injectable({ 10 | providedIn: 'root' 11 | }) 12 | export class AuthGuard implements CanActivate { 13 | private serverUrl: string; 14 | private user: User; 15 | authenticated: boolean = false; 16 | 17 | constructor( 18 | private userService: UserService, 19 | ) { 20 | this.serverUrl = `${environment.serverUrl}`; 21 | } 22 | 23 | canActivate( 24 | route: ActivatedRouteSnapshot, 25 | state: RouterStateSnapshot 26 | ): Observable | Promise | boolean | UrlTree { 27 | this.userService.fetchUser((user) => { 28 | this.user = user; 29 | }); 30 | if (this.user || this.userService.user?.getValue()) { 31 | return true; 32 | } 33 | return false; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /apps/groups/serializers/member_request.py: -------------------------------------------------------------------------------- 1 | from core.serializers import ModelReadOnlySerializer 2 | from groups.models import GroupMember, MemberRequest 3 | from rest_framework import serializers 4 | 5 | 6 | class MemberRequestReadOnlySerializer(ModelReadOnlySerializer): 7 | class Meta: 8 | model = MemberRequest 9 | fields = ("id", "group", "user", "is_approved") 10 | 11 | 12 | class MemberRequestSerializer(serializers.ModelSerializer): 13 | class Meta: 14 | model = MemberRequest 15 | fields = ("id", "group", "user", "is_approved") 16 | read_only_fields = ("id",) 17 | extra_kwargs = {"group": {"write_only": True}} 18 | 19 | def create(self, validated_data): 20 | request = self.context["request"] 21 | member_request = MemberRequest.objects.create(**validated_data) 22 | if member_request.group.group_type == "PUBLIC": 23 | member_request.is_approved = True 24 | member_request.save() 25 | if request.user: 26 | member, created = GroupMember.objects.get_or_create( 27 | group=member_request.group, user=request.user 28 | ) 29 | return member_request 30 | -------------------------------------------------------------------------------- /apps/groups/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.14 on 2022-11-16 09:00 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Group', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('created_at', models.DateTimeField(auto_now_add=True)), 19 | ('updated_at', models.DateTimeField(auto_now=True)), 20 | ('name', models.CharField(max_length=25)), 21 | ('description', models.TextField(blank=True)), 22 | ('group_type', models.CharField(choices=[('PUBLIC', 'Public'), ('RESTRICTED', 'Restricted'), ('PRIVATE', 'Private')], default='PUBLIC', max_length=15)), 23 | ('archive_posts', models.BooleanField(default=False, help_text='Posts after a period of X months will be archived automatically')), 24 | ], 25 | options={ 26 | 'abstract': False, 27 | }, 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment-edit/comment-edit.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 6 |
7 |
8 |
9 |
10 | 14 |
15 |
16 | 17 |
18 | 19 | 20 |
21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/components/report-dialog/report-dialog.component.scss: -------------------------------------------------------------------------------- 1 | .report-subtext { 2 | color: #707070; 3 | font-size: 12px; 4 | } 5 | 6 | .report-section { 7 | display: flex; 8 | flex-wrap: wrap; 9 | border: none; 10 | } 11 | 12 | .report-toggle-button, .report-toggle-button:hover, 13 | .report-toggle-button:focus { 14 | border: none; 15 | border-radius: 30px; 16 | font-size: 14px; 17 | 18 | &:active { 19 | background-color: #f14a25; 20 | color: white; 21 | } 22 | } 23 | 24 | .mat-button-toggle-group-appearance-standard 25 | .mat-button-toggle+.mat-button-toggle { 26 | border: none; 27 | } 28 | 29 | .mat-button-toggle-checked.mat-button-toggle-appearance-standard { 30 | background: #f15a24; 31 | color: white; 32 | } 33 | 34 | .mat-button-toggle-focus-overlay { 35 | opacity: 1!important; 36 | } 37 | 38 | .mat-button-toggle-checked .mat-button-toggle-focus-overlay { 39 | border-bottom: none!important; 40 | } 41 | 42 | .mat-button-toggle-appearance-standard .mat-button-toggle-label-content { 43 | line-height: 36px!important; 44 | } 45 | 46 | .flex-buttons { 47 | display: flex; 48 | gap: 16px; 49 | margin-top: 16px; 50 | justify-content: flex-end; 51 | } 52 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-bookmarks/profile-bookmarks.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { ActivatedRoute, Router } from '@angular/router'; 3 | import { UserService } from '@reddit/core/services/user/user.service'; 4 | import { User } from '@reddit/core/models/user.model'; 5 | import { Group } from '@reddit/core/models/group.model'; 6 | 7 | @Component({ 8 | selector: 'app-profile-bookmarks', 9 | templateUrl: './profile-bookmarks.component.html', 10 | styleUrls: ['./profile-bookmarks.component.scss'] 11 | }) 12 | export class ProfileBookmarksComponent implements OnInit { 13 | isLoading: boolean = false; 14 | @Input() user: User; 15 | bookmarks = []; 16 | @Input() self: boolean; 17 | @Input() currentUser: string; 18 | 19 | constructor( 20 | private userService: UserService, 21 | private route: ActivatedRoute, 22 | private router: Router 23 | ) { } 24 | 25 | 26 | ngOnInit(): void { 27 | this.getUserBookmarks(); 28 | } 29 | 30 | getUserBookmarks(){ 31 | this.userService.getBookmarks(this.currentUser).subscribe( 32 | (response: any) => { 33 | this.bookmarks = response; 34 | }) 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment-create/comment-create.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; 2 | import { Comment } from '@reddit/core/models/comment.model'; 3 | import { User } from '@reddit/core/models/user.model'; 4 | 5 | @Component({ 6 | selector: 'app-comment-create', 7 | templateUrl: './comment-create.component.html', 8 | styleUrls: ['./comment-create.component.scss'] 9 | }) 10 | export class CommentCreateComponent implements OnInit { 11 | @Input() user: User; 12 | @Input() uuid: string; 13 | @Input() nested: boolean; 14 | @Input() parent: number; 15 | @Input() child_group = false; 16 | @Input() mentioned_users: Set; 17 | 18 | @Output() comment_response = new EventEmitter() 19 | @Output() remove_mention = new EventEmitter() 20 | @Output() clear_comment = new EventEmitter() 21 | 22 | constructor() { } 23 | 24 | ngOnInit() { 25 | 26 | } 27 | 28 | emitComment(data: any) { 29 | this.comment_response.emit(data); 30 | } 31 | 32 | removeMention(data: any) { 33 | this.remove_mention.emit(data); 34 | } 35 | 36 | clearComment(data: any) { 37 | this.clear_comment.emit(data); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /apps/bookmarks/serializers.py: -------------------------------------------------------------------------------- 1 | from bookmarks.models import PostBookmark 2 | from core.serializers import ModelReadOnlySerializer 3 | from rest_framework import serializers 4 | 5 | 6 | class PostBookmarkSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = PostBookmark 9 | fields = ("id", "post", "user") 10 | read_only_fields = ("id",) 11 | extra_kwargs = {"post": {"write_only": True}} 12 | 13 | def create(self, validated_data): 14 | bookmark = PostBookmark.objects.create(**validated_data) 15 | return bookmark 16 | 17 | 18 | class PostBookmarkLightSerializer(serializers.ModelSerializer): 19 | class Meta: 20 | model = PostBookmark 21 | fields = ("id", "user") 22 | read_only_fields = ("id",) 23 | 24 | 25 | class PostBookmarkReadOnlySerializer(ModelReadOnlySerializer): 26 | post = serializers.SerializerMethodField() 27 | 28 | class Meta: 29 | model = PostBookmark 30 | fields = ("id", "post", "user") 31 | 32 | def get_post(self, obj): 33 | if hasattr(obj, "post"): 34 | from posts.serializers import PostReadOnlySerializer 35 | 36 | return PostReadOnlySerializer(obj.post).data 37 | return None 38 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/post/post-loader/post-loader.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Loading... 4 | 5 | 6 | 7 |
8 |
9 |
10 |
11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async () => { 7 | await TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | }); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'reddit-app'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.componentInstance; 26 | expect(app.title).toEqual('reddit-app'); 27 | }); 28 | 29 | it('should render title', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.nativeElement; 33 | expect(compiled.querySelector('.content span').textContent).toContain('reddit-app app is running!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /apps/profiles/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.14 on 2022-09-11 12:53 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='UserMetaInfo', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('created_at', models.DateTimeField(auto_now_add=True)), 22 | ('updated_at', models.DateTimeField(auto_now=True)), 23 | ('username_changed', models.DateField(blank=True, null=True, verbose_name='username_changed')), 24 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='meta_info', to=settings.AUTH_USER_MODEL, verbose_name='user')), 25 | ], 26 | options={ 27 | 'verbose_name': 'User Meta Info', 28 | 'verbose_name_plural': 'User Meta Info', 29 | }, 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/comments/comment-list/comment-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; 2 | import { Comment } from '@reddit/core/models/comment.model'; 3 | import { User } from '@reddit/core/models/user.model'; 4 | 5 | 6 | @Component({ 7 | selector: 'app-comment-list', 8 | templateUrl: './comment-list.component.html', 9 | styleUrls: ['./comment-list.component.scss'] 10 | }) 11 | export class CommentListComponent implements OnInit { 12 | @Input() comments: Comment[] = []; 13 | @Input() user: User; 14 | @Input() uuid: string; 15 | @Output() mentioned = new EventEmitter(); 16 | @Input() max_nest_depth: number = 0; 17 | @Input() current_nest_depth: number = 0; 18 | 19 | constructor() { } 20 | 21 | ngOnInit() { 22 | // console.log(this.comments, "list"); 23 | } 24 | 25 | removed(removed_comment: any) { 26 | this.comments.map((comment, index) => { 27 | if (comment.id == removed_comment.id) { 28 | this.comments.splice(index, 1); 29 | } 30 | }); 31 | } 32 | 33 | hightlight(data: any) { 34 | // console.log(data, "Highlight event"); 35 | } 36 | 37 | userMentioned(data: any) { 38 | console.log(data); 39 | this.mentioned.emit(data); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /apps/comments/admin.py: -------------------------------------------------------------------------------- 1 | from comments.models import PostComment 2 | from django.contrib import admin 3 | 4 | 5 | @admin.register(PostComment) 6 | class PostCommentAdmin(admin.ModelAdmin): 7 | list_display = ( 8 | "id", 9 | "user", 10 | "post", 11 | "has_children", 12 | "flair", 13 | "created_at", 14 | ) 15 | list_filter = ( 16 | "created_at", 17 | "updated_at", 18 | "user", 19 | "is_removed", 20 | "post", 21 | ) 22 | raw_id_fields = ("mentioned_users",) 23 | date_hierarchy = "created_at" 24 | fieldsets = ( 25 | ( 26 | "Comment Info", 27 | { 28 | "fields": ("user", "_comment", "flair", "parent"), 29 | }, 30 | ), 31 | ( 32 | "Competition Info", 33 | { 34 | "fields": ("post",), 35 | }, 36 | ), 37 | ( 38 | "Status", 39 | { 40 | "fields": ("is_removed", "is_nesting_permitted"), 41 | }, 42 | ), 43 | ("Other", {"fields": ("mentioned_users",)}), 44 | ) 45 | 46 | def has_children(self, obj): 47 | return PostComment.objects.filter(parent=obj).exists() 48 | 49 | has_children.boolean = True 50 | -------------------------------------------------------------------------------- /apps/followers/models.py: -------------------------------------------------------------------------------- 1 | from comments.models import PostComment 2 | from django.contrib.auth.models import User 3 | from django.db import models 4 | from followers.abstracts import AbstractFollower 5 | from posts.models import Post 6 | 7 | 8 | class PostFollower(AbstractFollower): 9 | post = models.ForeignKey( 10 | Post, verbose_name="post", on_delete=models.CASCADE, related_name="followers" 11 | ) 12 | 13 | class Meta: 14 | ordering = [ 15 | "-created_at", 16 | ] 17 | verbose_name = "Post Follower" 18 | verbose_name_plural = "Post Followers" 19 | 20 | def __str__(self): 21 | return f"{self.follower.username} started following {self.post.title}" 22 | 23 | 24 | class UserFollower(AbstractFollower): 25 | followed_user = models.ForeignKey( 26 | User, 27 | verbose_name="followed_user", 28 | on_delete=models.CASCADE, 29 | related_name="followers", 30 | ) 31 | 32 | class Meta: 33 | ordering = [ 34 | "-created_at", 35 | ] 36 | verbose_name = "User Follower" 37 | verbose_name_plural = "User Followers" 38 | 39 | def __str__(self): 40 | return ( 41 | f"{self.follower.username} started following {self.followed_user.username}" 42 | ) 43 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profiles/profiles.component.scss: -------------------------------------------------------------------------------- 1 | .profile-container { 2 | margin-top: 16px; 3 | border: 1px solid #dedede; 4 | border-radius: 8px; 5 | background-color: white; 6 | 7 | .content { 8 | padding: 16px; 9 | background-color: inherit; 10 | width: auto; 11 | } 12 | } 13 | 14 | .related-section { 15 | background-color: white; 16 | // height: 33%; 17 | min-height: 300px; 18 | margin-top: 16px; 19 | border: 1px solid #dedede; 20 | border-radius: 8px; 21 | padding: 16px; 22 | position: relative; 23 | 24 | &:first-child { 25 | padding: 0; 26 | } 27 | } 28 | 29 | .mat-label-label { 30 | color: #232323; 31 | opacity: 1; 32 | } 33 | 34 | 35 | .bg-blue { 36 | background: url('https://images.pexels.com/photos/3695238/pexels-photo-3695238.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1'); 37 | background-position: center; 38 | background-repeat: no-repeat; 39 | background-size: cover; 40 | border-radius: 8px 8px 0 0; 41 | height: 130px; 42 | width: inherit; 43 | } 44 | 45 | .user-avatar { 46 | position: absolute; 47 | top: 25%; 48 | left: 35%; 49 | } 50 | 51 | .user-content { 52 | margin-top: 60px; 53 | text-align: center; 54 | gap: 12px; 55 | } 56 | 57 | .user-meta-text { 58 | font-size: 14px; 59 | } 60 | -------------------------------------------------------------------------------- /static/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Django Reddit 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% block stylesheets %} 18 | 19 | {% endblock stylesheets %} 20 | 21 | {% block head_javascripts %} 22 | 23 | {% endblock head_javascripts %} 24 | 25 | 26 | 27 | 28 |

Django Reddit

29 | {% block javascripts %} 30 | 31 | {% endblock javascripts %} 32 | 33 | 34 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | # name: Deploy Angular to GitHub Pages 2 | 3 | # on: 4 | # push: 5 | # branches: 6 | # - main 7 | 8 | # permissions: 9 | # contents: write 10 | 11 | # jobs: 12 | # deploy: 13 | # runs-on: ubuntu-latest 14 | 15 | # steps: 16 | # - name: Checkout repository 17 | # uses: actions/checkout@v4 18 | # with: 19 | # fetch-depth: 0 20 | 21 | # - name: Debug - List repository files 22 | # run: ls -R 23 | 24 | # - name: Set up Node.js 25 | # uses: actions/setup-node@v4 26 | # with: 27 | # node-version: 14 28 | 29 | # - name: Install dependencies 30 | # run: | 31 | # cd ./static/frontend/reddit-app 32 | # npm install 33 | 34 | # - name: Install Angular 10 CLI 35 | # run: npm install -g @angular/cli@10 # Install Angular CLI globally 36 | 37 | # - name: Build Angular app 38 | # run: | 39 | # cd ./static/frontend/reddit-app 40 | # npx ng build --configuration production --base-href "/django_reddit/" 41 | 42 | # - name: Deploy to GitHub Pages 43 | # uses: JamesIves/github-pages-deploy-action@v4 44 | # with: 45 | # branch: gh-pages 46 | # folder: ./static/frontend/reddit-app/dist/reddit-app 47 | -------------------------------------------------------------------------------- /apps/groups/permissions.py: -------------------------------------------------------------------------------- 1 | from groups.models import Group 2 | from rest_framework import permissions 3 | 4 | 5 | class HasGroupEditPermissions(permissions.BasePermission): 6 | def has_object_permission(self, request, view, obj): 7 | user = request.user 8 | try: 9 | group_id = request.parser_context["kwargs"]["pk"] 10 | group = Group.objects.filter(id=group_id) 11 | except Exception as e: 12 | return False 13 | return user.has_perm("edit_groups", group) 14 | 15 | 16 | class HasGroupDeletePermissions(permissions.BasePermission): 17 | def has_object_permission(self, request, view, obj): 18 | user = request.user 19 | try: 20 | group_id = request.parser_context["kwargs"]["pk"] 21 | group = Group.objects.filter(id=group_id) 22 | except Exception as e: 23 | return False 24 | return user.has_perm("delete_groups", group) 25 | 26 | 27 | class HasAddMembersPermissions(permissions.BasePermission): 28 | def has_object_permission(self, request, view, obj): 29 | user = request.user 30 | try: 31 | member_id = request.parser_context["kwargs"]["pk"] 32 | member = Group.objects.filter(id=member_id) 33 | except Exception as e: 34 | return False 35 | return user.has_perm("add_members", member) 36 | -------------------------------------------------------------------------------- /apps/groups/migrations/0003_added_group_rules.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.14 on 2022-11-16 16:25 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('groups', '0002_added_group_members'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='GroupRule', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('created_at', models.DateTimeField(auto_now_add=True)), 19 | ('updated_at', models.DateTimeField(auto_now=True)), 20 | ('title', models.CharField(max_length=100)), 21 | ('description', models.CharField(max_length=500)), 22 | ('rule_type', models.CharField(choices=[('POSTS', 'Posts'), ('COMMENTS', 'Comments'), ('BOTH', 'Both')], default='BOTH', max_length=10)), 23 | ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rules', to='groups.group')), 24 | ], 25 | options={ 26 | 'verbose_name': 'Group Rule', 27 | 'verbose_name_plural': 'Group Rules', 28 | 'ordering': ['-created_at'], 29 | }, 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /apps/profiles/migrations/0002_auto_20220911_1305.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.14 on 2022-09-11 13:05 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('profiles', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='usermetainfo', 15 | name='bio', 16 | field=models.TextField(blank=True), 17 | ), 18 | migrations.AddField( 19 | model_name='usermetainfo', 20 | name='dob', 21 | field=models.DateField(null=True), 22 | ), 23 | migrations.AddField( 24 | model_name='usermetainfo', 25 | name='dob_visible', 26 | field=models.BooleanField(default=False), 27 | ), 28 | migrations.AddField( 29 | model_name='usermetainfo', 30 | name='is_admin', 31 | field=models.BooleanField(default=False), 32 | ), 33 | migrations.AddField( 34 | model_name='usermetainfo', 35 | name='is_banned', 36 | field=models.BooleanField(default=False), 37 | ), 38 | migrations.AddField( 39 | model_name='usermetainfo', 40 | name='is_requesting_delete', 41 | field=models.BooleanField(default=False), 42 | ), 43 | ] 44 | -------------------------------------------------------------------------------- /apps/bookmarks/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.14 on 2022-09-10 12:26 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ('posts', '0001_initial'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='PostBookmark', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('created_at', models.DateTimeField(auto_now_add=True)), 23 | ('updated_at', models.DateTimeField(auto_now=True)), 24 | ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bookmarks', to='posts.Post', verbose_name='post')), 25 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user')), 26 | ], 27 | options={ 28 | 'verbose_name': 'Post Bookmark', 29 | 'verbose_name_plural': 'Post Bookmarks', 30 | 'ordering': ['-created_at'], 31 | 'unique_together': {('post', 'user')}, 32 | }, 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /apps/groups/views/member.py: -------------------------------------------------------------------------------- 1 | from core.views import BaseReadOnlyViewSet, BaseViewSet 2 | from django.contrib.auth.models import User 3 | from groups.filters import GroupMemberFilterSet 4 | from groups.models import GroupMember 5 | from groups.serializers import GroupMemberSerializer 6 | from rest_framework import filters, generics, status, viewsets 7 | from rest_framework.decorators import action 8 | from rest_framework.pagination import PageNumberPagination 9 | from rest_framework.permissions import IsAuthenticated 10 | from rest_framework.response import Response 11 | 12 | 13 | class GroupMemberPagination(PageNumberPagination): 14 | page_size = 24 15 | 16 | 17 | class GroupMemberViewSet(BaseReadOnlyViewSet): 18 | queryset = GroupMember.objects.all() 19 | serializer_class = GroupMemberSerializer 20 | permission_classes = [ 21 | IsAuthenticated, 22 | ] 23 | pagination_class = GroupMemberPagination 24 | filterset_class = GroupMemberFilterSet 25 | 26 | def get_queryset(self): 27 | queryset = self.queryset 28 | if self.kwargs != {}: 29 | if "group_pk" in self.kwargs: 30 | queryset = queryset.filter(group__pk=self.kwargs["group_pk"]) 31 | return queryset 32 | 33 | # def list(self, request, group_pk=None): 34 | # queryset = self.filter_queryset(self.get_queryset()) 35 | # return self.paginated_response(queryset, context={'request': request}) 36 | -------------------------------------------------------------------------------- /apps/groups/models/group.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from core.models import TimeStampedModel 4 | from django.contrib.auth.models import User 5 | from django.db import models 6 | from tags.models import Tag 7 | 8 | 9 | class Group(TimeStampedModel): 10 | class Type(models.TextChoices): 11 | PUBLIC = "PUBLIC" 12 | RESTRICTED = "RESTRICTED" 13 | PRIVATE = "PRIVATE" 14 | 15 | name = models.CharField(max_length=25) 16 | description = models.TextField(blank=True) 17 | group_type = models.CharField( 18 | choices=Type.choices, 19 | max_length=15, 20 | default=Type.PUBLIC, 21 | help_text=""" 22 | PUBLIC: Anyone can view, post, and comment to this community.
23 | RESTRICTED: Anyone can view this community, but only approved users can post.
24 | PRIVATE: Only approved users can view and submit to this community. 25 | """, 26 | ) 27 | archive_posts = models.BooleanField( 28 | default=False, 29 | help_text="Posts after a period of X months will be archived automatically", 30 | ) 31 | topics = models.ManyToManyField( 32 | Tag, blank=True, verbose_name="topics", related_name="groups" 33 | ) 34 | 35 | class Meta: 36 | ordering = ["-created_at"] 37 | verbose_name = "Group" 38 | verbose_name_plural = "Groups" 39 | 40 | def __str__(self): 41 | return f"Group: {self.name}" 42 | -------------------------------------------------------------------------------- /apps/groups/migrations/0005_added_group_invites.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.14 on 2022-11-16 16:27 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('groups', '0004_added_group_invites'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='MemberRequest', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('created_at', models.DateTimeField(auto_now_add=True)), 21 | ('updated_at', models.DateTimeField(auto_now=True)), 22 | ('is_approved', models.BooleanField(default=False)), 23 | ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='member_requests', to='groups.group')), 24 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='requested_groups', to=settings.AUTH_USER_MODEL)), 25 | ], 26 | options={ 27 | 'verbose_name': 'Member Request', 28 | 'verbose_name_plural': 'Member Requests', 29 | 'ordering': ['-created_at'], 30 | }, 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /reddit_clone/settings/production.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | 3 | DEBUG = False 4 | 5 | SECRET_KEY = os.getenv("SECRET_KEY") 6 | 7 | ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "").split(",") 8 | 9 | # Production database (PostgreSQL example) 10 | DATABASES = { 11 | "default": { 12 | "ENGINE": "django.db.backends.postgresql_psycopg2", 13 | "NAME": os.getenv("DB_NAME"), 14 | "USER": os.getenv("DB_USER"), 15 | "PASSWORD": os.getenv("DB_PASSWORD"), 16 | "HOST": os.getenv("DB_HOST"), 17 | "PORT": os.getenv("DB_PORT", "5432"), 18 | } 19 | } 20 | 21 | # Production email settings 22 | EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" 23 | EMAIL_HOST = os.getenv("EMAIL_HOST") 24 | EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER") 25 | EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD") 26 | EMAIL_PORT = os.getenv("EMAIL_PORT", 587) 27 | EMAIL_USE_TLS = os.getenv("EMAIL_USE_TLS", "True").lower() == "true" 28 | 29 | # Security settings 30 | SECURE_SSL_REDIRECT = True 31 | SESSION_COOKIE_SECURE = True 32 | CSRF_COOKIE_SECURE = True 33 | SECURE_HSTS_SECONDS = 31536000 34 | SECURE_HSTS_INCLUDE_SUBDOMAINS = True 35 | SECURE_HSTS_PRELOAD = True 36 | X_FRAME_OPTIONS = "DENY" 37 | 38 | # Allauth Configuration for production 39 | ACCOUNT_EMAIL_VERIFICATION = "mandatory" 40 | SOCIALACCOUNT_EMAIL_VERIFICATION = "mandatory" 41 | 42 | # CORS settings for production 43 | CORS_ALLOWED_ORIGINS = os.getenv("CORS_ALLOWED_ORIGINS", "").split(",") 44 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/react-app/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/auth/sign-out/sign-out.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | 4 | import { environment } from '../../../environments/environment'; 5 | 6 | @Component({ 7 | selector: 'app-sign-out', 8 | templateUrl: './sign-out.component.html', 9 | styleUrls: ['./sign-out.component.scss'] 10 | }) 11 | export class SignOutComponent implements OnInit { 12 | fragment: string; 13 | context: any; 14 | 15 | redirection = { 16 | 'delete' : { 17 | title: 'You will be missed ;(', 18 | subtitle: `Your account shall be permanently deleted in 14 days. 19 | If you change your mind, please send us a request mail at admin@gmail.com 20 | and we shall set-up everything just like it was before.. ` 21 | }, 22 | 'deactivate' : { 23 | title: 'You will be missed ;(', 24 | subtitle: `Whenever you wish to reactivate your account, 25 | please send us a request mail at admin@gmail.com and we shall set-up 26 | everything just like it was before.` 27 | } 28 | } 29 | 30 | constructor( 31 | private route: ActivatedRoute 32 | ) { } 33 | 34 | ngOnInit(): void { 35 | this.route.fragment.subscribe((fragment: string) => { 36 | this.fragment = fragment; 37 | this.context = this.redirection[this.fragment] 38 | }) 39 | 40 | setTimeout(() => { 41 | window.open(`${environment.appUrl}`, '_self') 42 | }, 3000); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/react-app/src/components/auth/SignOut/SignOut.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import './SignOut.scss'; 3 | 4 | const SignOut = () => { 5 | const [context, setContext] = useState({ title: '', subtitle: '' }); 6 | const redirection: any = { 7 | 'delete' : { 8 | title: 'You will be missed ;(', 9 | subtitle: `Your account shall be permanently deleted in 14 days. 10 | If you change your mind, please send us a request mail at admin@gmail.com 11 | and we shall set-up everything just like it was before.. ` 12 | }, 13 | 'deactivate' : { 14 | title: 'You will be missed ;(', 15 | subtitle: `Whenever you wish to reactivate your account, 16 | please send us a request mail at admin@gmail.com and we shall set-up 17 | everything just like it was before.` 18 | } 19 | } 20 | useEffect(() => { 21 | const fragment = window.location.hash.substring(1); 22 | if (fragment && redirection[fragment]) { 23 | setContext(redirection[fragment]); 24 | } 25 | 26 | setTimeout(() => { 27 | window.open('/', '_self') 28 | }, 3000); 29 | }, []); 30 | 31 | return ( 32 |
33 |

{context.title}

34 |

35 | {context.subtitle} 36 |

37 |
38 | ); 39 | }; 40 | 41 | export default SignOut; 42 | -------------------------------------------------------------------------------- /apps/posts/migrations/0003_added_post_vote_model.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.14 on 2022-11-21 14:39 2 | 3 | from django.conf import settings 4 | import django.core.validators 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 13 | ('posts', '0002_altered_post_model'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='PostVote', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('created_at', models.DateTimeField(auto_now_add=True)), 22 | ('updated_at', models.DateTimeField(auto_now=True)), 23 | ('vote', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(-1), django.core.validators.MaxValueValidator(1)])), 24 | ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='votes', to='posts.post')), 25 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='post_votes', to=settings.AUTH_USER_MODEL)), 26 | ], 27 | options={ 28 | 'verbose_name': 'Post Vote', 29 | 'verbose_name_plural': 'Post Votes', 30 | 'ordering': ['created_at'], 31 | }, 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /apps/comments/models.py: -------------------------------------------------------------------------------- 1 | from comments.abstracts import AbstractComment, AbstractCommentVote 2 | from django.db import models 3 | from django.db.models import Sum 4 | from posts.models import Post 5 | 6 | 7 | class PostComment(AbstractComment): 8 | post = models.ForeignKey( 9 | Post, verbose_name="post", on_delete=models.CASCADE, related_name="comments" 10 | ) 11 | 12 | parent = models.ForeignKey( 13 | "self", null=True, related_name="comments", on_delete=models.CASCADE 14 | ) 15 | 16 | class Meta: 17 | ordering = [ 18 | "created_at", 19 | ] 20 | verbose_name = "Post Comment" 21 | verbose_name_plural = "Post Comments" 22 | 23 | def __str__(self): 24 | return f"Comment: {self.post.title} by {self.user.username}" 25 | 26 | def _get_score(self): 27 | score = 0 28 | votes = PostCommentVote.objects.filter(post_comment=self) 29 | if votes.exists(): 30 | score = votes.aggregate(Sum("vote"))["vote__sum"] or 0 31 | return score 32 | 33 | score = property(_get_score) 34 | 35 | 36 | class PostCommentVote(AbstractCommentVote): 37 | post_comment = models.ForeignKey( 38 | PostComment, on_delete=models.CASCADE, related_name="votes" 39 | ) 40 | 41 | class Meta: 42 | ordering = [ 43 | "created_at", 44 | ] 45 | verbose_name = "Post Comment Vote" 46 | verbose_name_plural = "Post Comment Votes" 47 | 48 | def __str__(self): 49 | return f"{self.vote} point by {self.user.username}" 50 | -------------------------------------------------------------------------------- /tags.csv: -------------------------------------------------------------------------------- 1 | name, type 2 | India, interest 3 | Cities & places, interest 4 | Funny, interest 5 | Jokes, interest 6 | Memes, interest 7 | Interesting, interest 8 | Lifehacks, interest 9 | Oddly satisfying, interest 10 | Heartwarming, interest 11 | History, interest 12 | Languages, interest 13 | Nature, interest 14 | Science, interest 15 | News, interest 16 | Tech, interest 17 | Gaming, interest 18 | Career , interest 19 | Movies , interest 20 | Sports, interest 21 | IPL, interest 22 | Football, interest 23 | Scifi, interest 24 | Fantasy, interest 25 | Music, interest 26 | Anime, interest 27 | Books, interest 28 | Comics, interest 29 | Art, interest 30 | Painting, interest 31 | Drawing, interest 32 | Digital art, interest 33 | Photography, interest 34 | Graphic design, interest 35 | Programming, interest 36 | DIY & crafts, interest 37 | Fashion, interest 38 | Tattoos, interest 39 | Makeup, interest 40 | Baking, interest 41 | Home decorating, interest 42 | Gardening, interest 43 | Fitness, interest 44 | Travel, interest 45 | Anime, general 46 | cars and motor vehicles, general 47 | celebrity, general 48 | crafts and DIY, general 49 | crypto, general 50 | politics, general 51 | law & courts, general 52 | culture race and ethinicity, general 53 | ethics and philosophy, general 54 | family and relationships, general 55 | fashion , general 56 | fitness and nutrition, general 57 | Gaming, general 58 | History, general 59 | hobbies, general 60 | Movies, general 61 | Tech, general 62 | Religion and spirituality, general 63 | technology, general 64 | Sports, general 65 | Programming, general 66 | literature, general 67 | -------------------------------------------------------------------------------- /apps/reports/abstracts.py: -------------------------------------------------------------------------------- 1 | from core.models import TimeStampedModel 2 | from django.contrib.auth.models import User 3 | from django.db import models 4 | from reports.models.types import ReportType 5 | 6 | 7 | class AbstractReport(TimeStampedModel): 8 | class STATUS(models.TextChoices): 9 | INITIATED = "INITIATED" 10 | VERIFIED = "VERIFIED" 11 | RESOLVED = "RESOLVED" 12 | REJECTED = "REJECTED" 13 | REDACTED = "REDACTED" 14 | 15 | STATUS_HELP_TEXT = """ 16 | 1. Anyone can create a report. Does not mean it is valid 17 | 2. Report is valid and further actions can be taken. 18 | 3. The Report was verified but is no longer valid. The problem has been solved. 19 | 4. Verified and found the report has no basis. Fake/ Invalid. 20 | 5. User is withdrawing the report. 21 | """ 22 | 23 | reporter = models.ForeignKey( 24 | User, 25 | on_delete=models.CASCADE, 26 | related_name="%(class)s_reports", 27 | ) 28 | report_type = models.ForeignKey( 29 | ReportType, 30 | null=True, 31 | on_delete=models.CASCADE, 32 | related_name="%(class)s_reports", 33 | ) 34 | url = models.TextField(blank=True) 35 | additional_info = models.TextField(blank=True) 36 | status = models.CharField( 37 | max_length=10, 38 | choices=STATUS.choices, 39 | default="INITIATED", 40 | help_text=STATUS_HELP_TEXT, 41 | ) 42 | 43 | class Meta: 44 | abstract = True 45 | ordering = [ 46 | "created_at", 47 | ] 48 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/react-app/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | .nav { 17 | position: absolute; 18 | top: 10px; 19 | right: 10px; 20 | display: flex; 21 | gap: 10px; 22 | } 23 | 24 | a { 25 | font-weight: 500; 26 | color: #646cff; 27 | text-decoration: inherit; 28 | } 29 | a:hover { 30 | color: #535bf2; 31 | } 32 | 33 | body { 34 | margin: 0; 35 | display: flex; 36 | place-items: center; 37 | min-width: 320px; 38 | min-height: 100vh; 39 | } 40 | 41 | h1 { 42 | font-size: 3.2em; 43 | line-height: 1.1; 44 | } 45 | 46 | button { 47 | border-radius: 8px; 48 | border: 1px solid transparent; 49 | padding: 0.6em 1.2em; 50 | font-size: 1em; 51 | font-weight: 500; 52 | font-family: inherit; 53 | background-color: #1a1a1a; 54 | cursor: pointer; 55 | transition: border-color 0.25s; 56 | } 57 | button:hover { 58 | border-color: #646cff; 59 | } 60 | button:focus, 61 | button:focus-visible { 62 | outline: 4px auto -webkit-focus-ring-color; 63 | } 64 | 65 | @media (prefers-color-scheme: light) { 66 | :root { 67 | color: #213547; 68 | background-color: #ffffff; 69 | } 70 | a:hover { 71 | color: #747bff; 72 | } 73 | button { 74 | background-color: #f9f9f9; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /apps/core/management/commands/populate_groups.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import random 3 | from pathlib import Path 4 | 5 | from django.contrib.auth.models import User 6 | from django.core.management.base import BaseCommand, CommandError 7 | from groups.models import Group, GroupMember 8 | from tags.models import Tag, TagType 9 | 10 | 11 | class Command(BaseCommand): 12 | help = "Populate groups and group members" 13 | 14 | def handle(self, *args, **options): 15 | path = Path("groups.csv").absolute() 16 | with open(path) as csvfile: 17 | reader = csv.DictReader(csvfile) 18 | for row in reader: 19 | name = row["name"].title() 20 | desc = row[" description"].lstrip().rstrip() 21 | type = row[" type"].strip() 22 | topic = row[" topic"].strip() 23 | 24 | tag_type = TagType.objects.filter(title=type).first() 25 | tag, created = Tag.objects.get_or_create(name=topic, tag_type=tag_type) 26 | group, created = Group.objects.get_or_create( 27 | name=name, 28 | description=desc, 29 | group_type=type, 30 | ) 31 | group.topics.add(tag) 32 | 33 | try: 34 | user = User.objects.get(id=group.id) 35 | admin, created = GroupMember.objects.get_or_create( 36 | user=user, group=group, member_type="ADMIN" 37 | ) 38 | print(admin.id) 39 | except User.DoesNotExist: 40 | pass 41 | -------------------------------------------------------------------------------- /apps/groups/migrations/0004_added_group_invites.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.14 on 2022-11-16 16:26 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('groups', '0003_added_group_rules'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='GroupInvite', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('created_at', models.DateTimeField(auto_now_add=True)), 21 | ('updated_at', models.DateTimeField(auto_now=True)), 22 | ('invite_as', models.CharField(choices=[('MEMBER', 'Member'), ('MODERATOR', 'Moderator')], default='MEMBER', max_length=10)), 23 | ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invitations', to=settings.AUTH_USER_MODEL)), 24 | ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invites', to='groups.group')), 25 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invites', to=settings.AUTH_USER_MODEL)), 26 | ], 27 | options={ 28 | 'verbose_name': 'Group Invite', 29 | 'verbose_name_plural': 'Group Invites', 30 | 'ordering': ['-created_at'], 31 | }, 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /static/frontend/reddit-app/angular/src/app/profiles/profile-downvotes/profile-downvotes.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { ActivatedRoute, Router } from '@angular/router'; 3 | import { UserService } from '@reddit/core/services/user/user.service'; 4 | import { CommentService } from '@reddit/core/services/comment/comment.service'; 5 | import { GroupService } from '@reddit/core/services/group/group.service'; 6 | import { User } from '@reddit/core/models/user.model'; 7 | import { Group } from '@reddit/core/models/group.model'; 8 | import { UserComment } from '@reddit/core/models/comment.model'; 9 | 10 | @Component({ 11 | selector: 'app-profile-downvotes', 12 | templateUrl: './profile-downvotes.component.html', 13 | styleUrls: ['./profile-downvotes.component.scss'] 14 | }) 15 | export class ProfileDownvotesComponent implements OnInit { 16 | isLoading: boolean = false; 17 | @Input() user: User; 18 | @Input() self: boolean; 19 | @Input() currentUser: string; 20 | 21 | downvotes = []; 22 | 23 | constructor( 24 | private userService: UserService, 25 | private groupService: GroupService, 26 | private commentService: CommentService, 27 | private route: ActivatedRoute, 28 | private router: Router 29 | ) { } 30 | 31 | ngOnInit(): void { 32 | this.getUserDownvotes(); 33 | } 34 | 35 | getUserDownvotes() { 36 | this.userService.userDownvotes(this.currentUser).subscribe( 37 | (response: any) => { 38 | const data = [...response.posts, ...response.comments]; 39 | this.downvotes = data.sort((a, b) => b.created_at - a.created_at); 40 | }); 41 | } 42 | } 43 | --------------------------------------------------------------------------------