├── 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
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 |
--------------------------------------------------------------------------------