Created backend structure, made editable homepage

This commit is contained in:
Barunes Padhy 2024-05-22 22:06:39 +03:00
parent 99720dd46d
commit 1be3ee54b0
82 changed files with 1666 additions and 230 deletions

174
backend/.gitignore vendored Normal file
View File

@ -0,0 +1,174 @@
# Created by https://www.toptal.com/developers/gitignore/api/django
# Edit at https://www.toptal.com/developers/gitignore?templates=django
### Django ###
*.log
*.pot
*.pyc
__pycache__/
local_settings.py
db.sqlite3
db.sqlite3-journal
media
# If your build process includes running collectstatic, then you probably don't need or want to include staticfiles/
# in your Git repository. Update and uncomment the following line accordingly.
# <django-project-name>/staticfiles/
### Django.Python Stack ###
# Byte-compiled / optimized / DLL files
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
# Django stuff:
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# End of https://www.toptal.com/developers/gitignore/api/django

View File

@ -0,0 +1,8 @@
import os
class MediaHandler:
def __init__(self, f, resource_type, resource_id):
pass
def handleUploadedFile(self):
pass

View File

View File

@ -0,0 +1,10 @@
from django.contrib import admin
from .models import (
UserData,
Category,
Blog
)
admin.site.register(UserData)
admin.site.register(Category)
admin.site.register(Blog)

View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class ApimanagerConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apimanager'

View File

@ -0,0 +1,8 @@
userdata = {
"name": "Jhon Doe",
"intro_content": "<p>Write something about yourself</p>",
"profile_photo": "",
"default_theme": "darkTheme",
"dark_theme": "{\"darkTheme\": { \"theme\": \"Dark Mode\",\"background\": \"bg-dark\",\"textColor\": \"text-white\",\"linkBackground\": \"bg-light\",\"linkTextColor\": \"text-black\",\"captionColor\": \"#8a8a8a\",\"fontAwesomeIcon\": \"faSun\",\"borderColor\": \"secondary\",\"buttonColor\": \"light\",\"navBar\": {\"navBarTheme\": \"navbar-dark\",\"background\": \"bg-secondary\",\"buttonColor\": \"light\"},\"footer\": {\"background\": \"bg-secondary\",\"text\": \"bg-white\"}}}",
"light_theme": "{\"lightTheme\":{\"theme\": \"Light Mode\",\"background\": \"bg-light\",\"textColor\": \"text-black\",\"linkBackground\": \"bg-dark\",\"linkTextColor\": \"text-white\",\"captionColor\": \"#605f5f\",\"fontAwesomeIcon\": \"faMoon\",\"borderColor\": \"secondary\",\"buttonColor\": \"dark\",\"navBar\": {\"navBarTheme\": \"navbar-light\",\"background\": \"bg-secondary\",\"buttonColor\": \"light\"},\"footer\": {\"background\": \"bg-secondary\",\"text\": \"bg-white\"}}}"
}

View File

@ -0,0 +1,49 @@
# Generated by Django 5.0.4 on 2024-05-18 14:55
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Category',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('resourceid', models.SlugField()),
('featuredid', models.SlugField()),
('name', models.CharField(blank=True, max_length=200, null=True)),
('description', models.CharField(blank=True, max_length=200, null=True)),
('tagline', models.CharField(blank=True, max_length=200, null=True)),
('coverimage', models.CharField(blank=True, max_length=500, null=True)),
],
),
migrations.CreateModel(
name='UserData',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('introContent', models.CharField(blank=True, max_length=100000, null=True)),
('profilePhoto', models.CharField(blank=True, max_length=500, null=True)),
],
),
migrations.CreateModel(
name='Blog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('resourceid', models.SlugField()),
('name', models.CharField(blank=True, max_length=200, null=True)),
('description', models.CharField(blank=True, max_length=200, null=True)),
('tagline', models.CharField(blank=True, max_length=200, null=True)),
('coverimage', models.CharField(blank=True, max_length=500, null=True)),
('contentbody', models.CharField(blank=True, max_length=100000, null=True)),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='blogs', to='apimanager.category')),
],
),
]

View File

@ -0,0 +1,53 @@
# Generated by Django 5.0.4 on 2024-05-18 15:43
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('apimanager', '0001_initial'),
]
operations = [
migrations.RenameField(
model_name='blog',
old_name='contentbody',
new_name='content_body',
),
migrations.RenameField(
model_name='blog',
old_name='coverimage',
new_name='cover_image',
),
migrations.RenameField(
model_name='blog',
old_name='resourceid',
new_name='resource_id',
),
migrations.RenameField(
model_name='category',
old_name='coverimage',
new_name='cover_image',
),
migrations.RenameField(
model_name='category',
old_name='featuredid',
new_name='featured_id',
),
migrations.RenameField(
model_name='category',
old_name='resourceid',
new_name='resource_id',
),
migrations.RenameField(
model_name='userdata',
old_name='introContent',
new_name='intro_content',
),
migrations.RenameField(
model_name='userdata',
old_name='profilePhoto',
new_name='profile_photo',
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 5.0.4 on 2024-05-18 16:13
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('apimanager', '0002_rename_contentbody_blog_content_body_and_more'),
]
operations = [
migrations.RenameField(
model_name='blog',
old_name='resource_id',
new_name='blog_id',
),
migrations.RenameField(
model_name='category',
old_name='resource_id',
new_name='category_id',
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 5.0.4 on 2024-05-19 07:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('apimanager', '0003_rename_resource_id_blog_blog_id_and_more'),
]
operations = [
migrations.AddField(
model_name='userdata',
name='dark_theme',
field=models.CharField(default='m', max_length=1500),
),
migrations.AddField(
model_name='userdata',
name='defaut_theme',
field=models.CharField(default='m', max_length=200),
),
migrations.AddField(
model_name='userdata',
name='light_theme',
field=models.CharField(default='m', max_length=1500),
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 5.0.4 on 2024-05-19 07:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('apimanager', '0004_userdata_dark_theme_userdata_defaut_theme_and_more'),
]
operations = [
migrations.AlterField(
model_name='userdata',
name='dark_theme',
field=models.CharField(max_length=1500),
),
migrations.AlterField(
model_name='userdata',
name='defaut_theme',
field=models.CharField(max_length=200),
),
migrations.AlterField(
model_name='userdata',
name='light_theme',
field=models.CharField(max_length=1500),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.4 on 2024-05-19 07:19
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('apimanager', '0005_alter_userdata_dark_theme_and_more'),
]
operations = [
migrations.RenameField(
model_name='userdata',
old_name='defaut_theme',
new_name='default_theme',
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.4 on 2024-05-19 07:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('apimanager', '0006_rename_defaut_theme_userdata_default_theme'),
]
operations = [
migrations.AlterField(
model_name='category',
name='featured_id',
field=models.CharField(blank=True, max_length=500, null=True),
),
]

View File

@ -0,0 +1,38 @@
# Generated by Django 5.0.4 on 2024-05-19 08:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('apimanager', '0007_alter_category_featured_id'),
]
operations = [
migrations.AlterField(
model_name='userdata',
name='dark_theme',
field=models.CharField(default='{"darkTheme": { "theme": "Dark Mode","background": "bg-dark","textColor": "text-white","linkBackground": "bg-light","linkTextColor": "text-black","captionColor": "#8a8a8a","fontAwesomeIcon": "faSun","borderColor": "secondary","buttonColor": "light","navBar": {"navBarTheme": "navbar-dark","background": "bg-secondary","buttonColor": "light"},"footer": {"background": "bg-secondary","text": "bg-white"}}}', max_length=1500),
),
migrations.AlterField(
model_name='userdata',
name='default_theme',
field=models.CharField(default='darkTheme', max_length=200),
),
migrations.AlterField(
model_name='userdata',
name='intro_content',
field=models.CharField(default='<p>Write something about yourself</p>', max_length=100000),
),
migrations.AlterField(
model_name='userdata',
name='light_theme',
field=models.CharField(default='{"lightTheme":{"theme": "Light Mode","background": "bg-light","textColor": "text-black","linkBackground": "bg-dark","linkTextColor": "text-white","captionColor": "#605f5f","fontAwesomeIcon": "faMoon","borderColor": "secondary","buttonColor": "dark","navBar": {"navBarTheme": "navbar-light","background": "bg-secondary","buttonColor": "light"},"footer": {"background": "bg-secondary","text": "bg-white"}}}', max_length=1500),
),
migrations.AlterField(
model_name='userdata',
name='name',
field=models.CharField(default='Jhon Doe', max_length=200),
),
]

View File

@ -0,0 +1,48 @@
# Generated by Django 5.0.4 on 2024-05-19 08:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('apimanager', '0008_alter_userdata_dark_theme_and_more'),
]
operations = [
migrations.AlterField(
model_name='blog',
name='content_body',
field=models.CharField(default='<p></p>', max_length=100000),
),
migrations.AlterField(
model_name='blog',
name='description',
field=models.CharField(max_length=200),
),
migrations.AlterField(
model_name='blog',
name='name',
field=models.CharField(max_length=200),
),
migrations.AlterField(
model_name='blog',
name='tagline',
field=models.CharField(max_length=200),
),
migrations.AlterField(
model_name='category',
name='description',
field=models.CharField(max_length=200),
),
migrations.AlterField(
model_name='category',
name='name',
field=models.CharField(max_length=200),
),
migrations.AlterField(
model_name='category',
name='tagline',
field=models.CharField(max_length=200),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.4 on 2024-05-19 15:55
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('apimanager', '0009_alter_blog_content_body_alter_blog_description_and_more'),
]
operations = [
migrations.RenameField(
model_name='blog',
old_name='category',
new_name='parent_category',
),
]

View File

@ -0,0 +1,28 @@
from django.db import models
from .initialize_data import userdata
class UserData(models.Model):
name = models.CharField(default=userdata["name"], null=False, blank=False, max_length=200)
intro_content = models.CharField(default=userdata["intro_content"], null=False, blank=False, max_length=100000)
profile_photo = models.CharField(null=True, blank=True, max_length=500)
default_theme = models.CharField(default=userdata["default_theme"], null=False, blank=False, max_length=200)
dark_theme = models.CharField(default=userdata["dark_theme"], null=False, blank=False, max_length=1500)
light_theme = models.CharField(default=userdata["light_theme"], null=False, blank=False, max_length=1500)
class Category(models.Model):
category_id = models.SlugField()
featured_id = models.CharField(null=True, blank=True, max_length=500)
name = models.CharField(null=False, blank=False, max_length=200)
description = models.CharField(null=False, blank=False, max_length=200)
tagline = models.CharField(null=False, blank=False, max_length=200)
cover_image = models.CharField(null=True, blank=True, max_length=500)
class Blog(models.Model):
blog_id = models.SlugField()
name = models.CharField(null=False, blank=False, max_length=200)
description = models.CharField(null=False, blank=False, max_length=200)
tagline = models.CharField(null=False, blank=False, max_length=200)
cover_image = models.CharField(null=True, blank=True, max_length=500)
content_body = models.CharField(default='<p></p>', null=False, blank=False, max_length=100000)
parent_category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="blogs")

View File

@ -0,0 +1,66 @@
from django.contrib.auth import get_user_model, authenticate, login, logout
from django.contrib.auth.models import User
from django.db.models import Q
from django.urls import reverse
from django.utils import timezone
from rest_framework import serializers
from .models import (
UserData,
Category,
Blog
)
class UserDataSerializer(serializers.ModelSerializer):
class Meta:
model = UserData
fields = [
'name',
'intro_content',
'profile_photo',
]
class ThemeDataSerializer(serializers.ModelSerializer):
class Meta:
model = UserData
fields = [
'default_theme',
'dark_theme',
'light_theme'
]
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = [
'category_id',
'featured_id',
'name',
'description',
'tagline',
'cover_image'
]
class BlogSerializer(serializers.ModelSerializer):
class Meta:
model = Blog
fields = [
'blog_id',
'name',
'description',
'tagline',
'cover_image',
'content_body',
'parent_category'
]
class MediaSerializer(serializers.Serializer):
media = serializers.ListField(
child=serializers.FileField(max_length=100000, allow_empty_file=False, use_url=False)
)
resource_type = serializers.CharField(allow_blank=False)
resource_id = serializers.CharField(allow_blank=False)

View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

199
backend/apimanager/views.py Normal file
View File

@ -0,0 +1,199 @@
#######################Django related imports####################
import os
import subprocess
import ast
import shutil
from django.shortcuts import render
from django.contrib.auth.models import User
from django.http import HttpResponseRedirect
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.decorators import api_view
from rest_framework.generics import GenericAPIView
from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework import generics, permissions, views, serializers, status
#################################################################
#API related imports
from .models import (
UserData,
Category,
Blog
)
from .serializers import (
UserDataSerializer,
ThemeDataSerializer,
CategorySerializer,
BlogSerializer,
MediaSerializer
)
################################################################
#Custom Imports
from .MediaHandler import (
MediaHandler,
)
#UserData related views#############################################
class UserDataUpdateAPIView(generics.RetrieveUpdateAPIView):
serializer_class = UserDataSerializer
def get_object(self):
obj, created = UserData.objects.get_or_create(pk=1)
return obj
class UserDataListAPIView(generics.ListAPIView):
queryset = UserData.objects.all()
serializer_class = UserDataSerializer
#####################################################################
#ThemeData related views#############################################
class ThemeDataUpdateAPIView(generics.RetrieveUpdateAPIView):
serializer_class = ThemeDataSerializer
def get_object(self):
obj, created = UserData.objects.get_or_create(pk=1)
return obj
class ThemeDataListAPIView(generics.ListAPIView):
queryset = UserData.objects.all()
serializer_class = ThemeDataSerializer
#####################################################################
#Category related views#############################################
class CategoryCreateAPIView(generics.CreateAPIView):
queryset = Category.objects.all()
serializer_class = CategorySerializer
lookup_field = 'category_id'
class CategoryUpdateAPIView(generics.RetrieveUpdateAPIView):
queryset = Category.objects.all()
serializer_class = CategorySerializer
lookup_field = 'category_id'
class CategoryListAPIView(generics.ListAPIView):
queryset = Category.objects.all()
serializer_class = CategorySerializer
class CategoryDeleteAPIView(generics.DestroyAPIView):
queryset = Category.objects.all()
serializer_class = CategorySerializer
lookup_field = 'category_id'
################################################################
#Blog related views##################################################
class BlogCreateAPIView(generics.CreateAPIView):
queryset = Blog.objects.all()
serializer_class = BlogSerializer
lookup_field = 'blog_id'
class BlogUpdateAPIView(generics.RetrieveUpdateAPIView):
queryset = Blog.objects.all()
serializer_class = BlogSerializer
lookup_field = 'blog_id'
class BlogRetrieveAPIView(generics.RetrieveAPIView):
queryset = Blog.objects.all()
serializer_class = BlogSerializer
lookup_field = 'blog_id'
class BlogsByCategoryAPIView(APIView):
def get(self, request, category_id):
try:
category = Category.objects.get(category_id=category_id)
except Category.DoesNotExist:
return Response({'message': 'Category not found'}, status=404)
blogs = category.blogs.all()
serializer = BlogSerializer(blogs, many=True)
return Response(serializer.data)
class BlogDeleteAPIView(generics.DestroyAPIView):
queryset = Blog.objects.all()
serializer_class = BlogSerializer
lookup_field = 'blog_id'
####################################################################
'''
class MediaView(APIView):
parser_classes = (MultiPartParser, FormParser)
def post(self, request, *args, **kwargs):
file_serializer = FileSerializer(data=request.data)
if file_serializer.is_valid():
files = dict((f, f) for f in request.FILES.getlist('file'))
nodeName = file_serializer.validated_data['nodeName']
preferredFormat = file_serializer.validated_data['preferredFormat']
for f in files.values():
fileHandlerObject = FileHandler(f, preferredFormat, nodeName)
fileProcessed = fileHandlerObject.handleUploadedFile()
if not fileProcessed[0]:
return Response(file_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
return Response(file_serializer.data, status=status.HTTP_201_CREATED)
return Response(file_serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(file_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class ETLFunctions(GenericAPIView):
serializer_class = ETLData
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
data = serializer.data
if data['operation'] == "create-folder":
os.mkdir('../Analysis/'+data['postData'])
with open('../Analysis/'+data['postData']+'/'+data['postData']+'-run.log', 'w') as fp:
pass
fp.close()
if data['operation'] == "rename-folder":
os.rename('../Analysis/'+data['oldTitle'], '../Analysis/'+data['postData'])
if data['operation'] == "create-partition-file":
print(os.getcwd())
print(os.listdir(os.getcwd()))
partInfo = (data['postData'].split('|'))
with open(f'"{partInfo[0]}.part"', "w") as fpp:
pass
fpp.close()
partitionFile = open(f'"{partInfo[0]}.part"', "w+")
partitionFile.write(partInfo[1])
partitionFile.close()
if data['operation'] == "move-file":
pass
return Response("Success", status=status.HTTP_200_OK)
class BioTools(APIView):
def get(self, request):
params = request.GET.get('function', '')
params = params.split(";")
output = subprocess.check_output(f'seqmagick extract-ids ../Analysis/"{params[1]}"/"{(params[2])[:-1]}"', shell=True)
outgroups = (output.decode("utf-8")).split('\n')
outgroups = outgroups[:len(outgroups)-1]
return Response({'outgroups': outgroups})
class CommandRunner(GenericAPIView):
serializer_class = InterimData
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
data = serializer.data
runLogFile = f'../Analysis/{data["nodeName"]}/{data["nodeName"]}-run.log'
try:
commands = ast.literal_eval(data['finalParameter'])
for key, value in commands.items():
process = subprocess.Popen(value+f" > {runLogFile}", shell=True)
except:
process = subprocess.Popen(data['finalParameter']+f" > {runLogFile}", shell=True)
return Response("Command successfully sent for execution", status=status.HTTP_200_OK)
'''

View File

16
backend/backend/asgi.py Normal file
View File

@ -0,0 +1,16 @@
"""
ASGI config for backend project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
application = get_asgi_application()

165
backend/backend/settings.py Normal file
View File

@ -0,0 +1,165 @@
"""
Django settings for backend project.
Generated by 'django-admin startproject' using Django 5.0.4.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.0/ref/settings/
"""
from pathlib import Path
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-2qks!5e#imys-r@tp2t#%cc3!*apkfu9f-(a7t)bn%sm@@3aq+'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'apimanager'
]
TINYMCE_DEFAULT_CONFIG = {
'height': 360,
'width': 1120,
'cleanup_on_startup': True,
'custom_undo_redo_levels': 20,
'selector': 'textarea',
'theme': 'silver',
'plugins': '''
textcolor save link image media preview codesample contextmenu
table code lists fullscreen insertdatetime nonbreaking
contextmenu directionality searchreplace wordcount visualblocks
visualchars code fullscreen autolink lists charmap print hr
anchor pagebreak spellchecker
''',
'toolbar1': '''
fullscreen preview bold italic underline | fontselect,
fontsizeselect | forecolor backcolor | alignleft alignright |
aligncenter alignjustify | indent outdent | bullist numlist table |
| link image media | codesample |
''',
'toolbar2': '''
visualblocks visualchars |
charmap hr pagebreak nonbreaking anchor | code |
''',
'contextmenu': 'formats | link image',
'menubar': True,
'statusbar': True,
}
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
]
}
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'backend.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'backend.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
CSRF_TRUSTED_ORIGINS = [
'http://localhost:3000',
]

52
backend/backend/urls.py Normal file
View File

@ -0,0 +1,52 @@
"""
URL configuration for backend project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, re_path
from django.conf.urls import include
from .views import my_form
from apimanager.views import (
UserDataUpdateAPIView,
UserDataListAPIView,
ThemeDataUpdateAPIView,
ThemeDataListAPIView,
CategoryCreateAPIView,
CategoryUpdateAPIView,
CategoryDeleteAPIView,
CategoryListAPIView,
BlogCreateAPIView,
BlogUpdateAPIView,
BlogRetrieveAPIView,
BlogDeleteAPIView,
BlogsByCategoryAPIView
)
urlpatterns = [
path('admin/', admin.site.urls),
path('data/shared/user-data/', UserDataListAPIView.as_view(), name='user-data-list-view'),
path('data/shared/update/user-data/', UserDataUpdateAPIView.as_view(), name='user-data-update-view'),
path('data/shared/theme-config/', ThemeDataListAPIView.as_view(), name='theme-data-list-view'),
path('data/shared/update/theme-config/', ThemeDataUpdateAPIView.as_view(), name='theme-data-update-view'),
path('data/category/', CategoryListAPIView.as_view(), name='category-list-view'),
path('data/category/<slug:category_id>/', BlogsByCategoryAPIView.as_view(), name='blogs-by-category-view'),
path('data/category/create/<slug:category_id>/', CategoryCreateAPIView.as_view(), name='category-create-view'),
path('data/category/update/<slug:category_id>/', CategoryUpdateAPIView.as_view(), name='category-update-view'),
path('data/category/delete/<slug:category_id>/', CategoryDeleteAPIView.as_view(), name='category-delete-view'),
path('data/blog/<slug:blog_id>/', BlogRetrieveAPIView.as_view(), name='blog-retrieve-view'),
path('data/blog/create/<slug:blog_id>/', BlogCreateAPIView.as_view(), name='blog-create-view'),
path('data/blog/update/<slug:blog_id>/', BlogUpdateAPIView.as_view(), name='blog-update-view'),
path('data/blog/delete/<slug:blog_id>/', BlogDeleteAPIView.as_view(), name='blog-delete-view'),
]

4
backend/backend/views.py Normal file
View File

@ -0,0 +1,4 @@
from django.shortcuts import render
def my_form(request):
return render(request, 'main')

16
backend/backend/wsgi.py Normal file
View File

@ -0,0 +1,16 @@
"""
WSGI config for backend project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
application = get_wsgi_application()

22
backend/manage.py Executable file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,9 @@
{
"id": "72e4d550-a19b-4b62-bf5a-13f98813d31a",
"name": "Blog 1",
"description": "A subtitle for Blog 1",
"tagLine": "Read blog",
"coverImage": "blogs/72e4d550-a19b-4b62-bf5a-13f98813d31a/media/blog1.png",
"parentCategory": "520b7982-069e-4a48-9ef3-64507d86a579",
"contentBody": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec non ipsum in nunc pretium gravida id ut lectus. Duis ligula nisl, egestas a tortor nec, scelerisque hendrerit urna. Nullam gravida, ante id aliquet ultrices, justo metus aliquet augue, vestibulum posuere massa lacus at est. Cras vitae dolor euismod, volutpat quam eu, cursus enim. Maecenas in magna ut augue ultrices laoreet. Maecenas sapien sem, mollis sed ipsum nec, viverra vehicula quam. Sed et pulvinar justo. Quisque et vestibulum dui. Aliquam laoreet tempus neque, et eleifend nulla vestibulum eget. Cras tempus justo at nunc facilisis auctor. Duis facilisis tortor eu risus aliquam dapibus nec sit amet est. Maecenas ante lectus, sagittis eu facilisis sit amet, convallis eu ex. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean imperdiet vulputate ipsum sed scelerisque. Donec sit amet rutrum est. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam gravida augue sem, quis aliquet justo varius non. Nam tortor nulla, bibendum sit amet finibus sed, ultricies non tellus. Aliquam condimentum risus ut felis porta iaculis. Nullam gravida mauris lacinia finibus hendrerit. Sed nec consectetur erat, sed tincidunt velit. Donec sit amet nulla at sem blandit imperdiet ut eu nisl. Sed condimentum, lectus quis sodales commodo, arcu turpis sodales ex, ac placerat mi turpis sit amet mi. Nullam lorem velit, porta eu quam eu, hendrerit egestas nibh. Sed non pellentesque arcu. Nam felis lectus, scelerisque in semper a, faucibus sit amet nibh. Cras est ligula, pretium id maximus nec, hendrerit eu ante. Maecenas tincidunt est ante, sed vestibulum mauris dignissim nec. Phasellus varius varius leo in pharetra. Aenean vestibulum id dui cursus lobortis. Mauris vel orci massa. Nullam vitae lorem mattis, lobortis dui in, pharetra velit. Aenean non urna ac felis volutpat consequat sit amet sed nisi. Quisque rutrum nisi ac erat ultricies tempor. Etiam nec pellentesque metus. Nulla tempus mi a ex rutrum, ut luctus tellus porta. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus et quam sit amet arcu mattis commodo quis non risus. Sed luctus non dui ullamcorper consectetur. Duis eros magna, tempus ut aliquam sit amet, tempor vel nibh. Vestibulum hendrerit odio convallis elit pretium dictum. Proin ligula dolor, finibus eget lacus sed, facilisis fringilla lorem. Quisque quis lacus sit amet massa blandit fringilla vel vitae tortor. Vivamus dictum nibh vel justo ullamcorper faucibus. Nullam vitae augue pretium erat semper rhoncus. Etiam tempus, arcu feugiat hendrerit pretium, nisi justo sollicitudin velit, sagittis elementum ante metus sed ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam quis ligula euismod, venenatis arcu vitae, condimentum arcu. Donec vitae nisl aliquam, rutrum arcu vel, sollicitudin justo. Vestibulum nec sagittis massa. Etiam maximus, erat vitae dapibus vulputate, velit lectus imperdiet est, sed lobortis ante erat lobortis arcu. Maecenas mollis nunc ut nisi tristique tempus. Nulla tempor est non dui scelerisque, eget semper augue consequat."
}

View File

@ -0,0 +1,9 @@
{
"id": "b4d9e1a0-4a77-48eb-a04b-06ec23e2b73e",
"name": "Blog 2",
"description": "A subtitle for Blog 2",
"tagLine": "Read blog",
"coverImage": "blogs/b4d9e1a0-4a77-48eb-a04b-06ec23e2b73e/media/blog2.png",
"parentCategory": "520b7982-069e-4a48-9ef3-64507d86a579",
"contentBody": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec non ipsum in nunc pretium gravida id ut lectus. Duis ligula nisl, egestas a tortor nec, scelerisque hendrerit urna. Nullam gravida, ante id aliquet ultrices, justo metus aliquet augue, vestibulum posuere massa lacus at est. Cras vitae dolor euismod, volutpat quam eu, cursus enim. Maecenas in magna ut augue ultrices laoreet. Maecenas sapien sem, mollis sed ipsum nec, viverra vehicula quam. Sed et pulvinar justo. Quisque et vestibulum dui. Aliquam laoreet tempus neque, et eleifend nulla vestibulum eget. Cras tempus justo at nunc facilisis auctor. Duis facilisis tortor eu risus aliquam dapibus nec sit amet est. Maecenas ante lectus, sagittis eu facilisis sit amet, convallis eu ex. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean imperdiet vulputate ipsum sed scelerisque. Donec sit amet rutrum est. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam gravida augue sem, quis aliquet justo varius non. Nam tortor nulla, bibendum sit amet finibus sed, ultricies non tellus. Aliquam condimentum risus ut felis porta iaculis. Nullam gravida mauris lacinia finibus hendrerit. Sed nec consectetur erat, sed tincidunt velit. Donec sit amet nulla at sem blandit imperdiet ut eu nisl. Sed condimentum, lectus quis sodales commodo, arcu turpis sodales ex, ac placerat mi turpis sit amet mi. Nullam lorem velit, porta eu quam eu, hendrerit egestas nibh. Sed non pellentesque arcu. Nam felis lectus, scelerisque in semper a, faucibus sit amet nibh. Cras est ligula, pretium id maximus nec, hendrerit eu ante. Maecenas tincidunt est ante, sed vestibulum mauris dignissim nec. Phasellus varius varius leo in pharetra. Aenean vestibulum id dui cursus lobortis. Mauris vel orci massa. Nullam vitae lorem mattis, lobortis dui in, pharetra velit. Aenean non urna ac felis volutpat consequat sit amet sed nisi. Quisque rutrum nisi ac erat ultricies tempor. Etiam nec pellentesque metus. Nulla tempus mi a ex rutrum, ut luctus tellus porta. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus et quam sit amet arcu mattis commodo quis non risus. Sed luctus non dui ullamcorper consectetur. Duis eros magna, tempus ut aliquam sit amet, tempor vel nibh. Vestibulum hendrerit odio convallis elit pretium dictum. Proin ligula dolor, finibus eget lacus sed, facilisis fringilla lorem. Quisque quis lacus sit amet massa blandit fringilla vel vitae tortor. Vivamus dictum nibh vel justo ullamcorper faucibus. Nullam vitae augue pretium erat semper rhoncus. Etiam tempus, arcu feugiat hendrerit pretium, nisi justo sollicitudin velit, sagittis elementum ante metus sed ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam quis ligula euismod, venenatis arcu vitae, condimentum arcu. Donec vitae nisl aliquam, rutrum arcu vel, sollicitudin justo. Vestibulum nec sagittis massa. Etiam maximus, erat vitae dapibus vulputate, velit lectus imperdiet est, sed lobortis ante erat lobortis arcu. Maecenas mollis nunc ut nisi tristique tempus. Nulla tempor est non dui scelerisque, eget semper augue consequat."
}

View File

@ -0,0 +1,18 @@
[
{
"id": "72e4d550-a19b-4b62-bf5a-13f98813d31a",
"name": "Blog 1",
"description": "A subtitle for Blog 1",
"coverImage": "blogs/72e4d550-a19b-4b62-bf5a-13f98813d31a/media/blog1.png",
"tagLine": "Read more",
"parentCategory": "520b7982-069e-4a48-9ef3-64507d86a579"
},
{
"id": "b4d9e1a0-4a77-48eb-a04b-06ec23e2b73e",
"name": "Blog 2",
"description": "A subtitle for Blog 2",
"coverImage": "blogs/b4d9e1a0-4a77-48eb-a04b-06ec23e2b73e/media/blog2.png",
"tagLine": "Read more",
"parentCategory": "520b7982-069e-4a48-9ef3-64507d86a579"
}
]

View File

@ -0,0 +1,8 @@
{
"id": "520b7982-069e-4a48-9ef3-64507d86a579",
"name": "Technology",
"coverImage": "category/520b7982-069e-4a48-9ef3-64507d86a579/media/technology.png",
"tagLine": "Read articles about tech",
"description": "I have been working in tech for long, and here are my thoughts of random stuff",
"featuredBlog": "b4d9e1a0-4a77-48eb-a04b-06ec23e2b73e"
}

View File

@ -0,0 +1,7 @@
{
"id": "b9e0d686-351d-49af-8e3d-b62023f44dbe",
"name": "Gaming",
"coverImage": "category/b9e0d686-351d-49af-8e3d-b62023f44dbe/media/game.png",
"tagLine": "Read articles about games",
"description": "I like to game, and here are my thoughts on games"
}

View File

@ -0,0 +1,16 @@
[
{
"id": "520b7982-069e-4a48-9ef3-64507d86a579",
"name": "Technology",
"coverImage": "category/520b7982-069e-4a48-9ef3-64507d86a579/media/technology.png",
"tagLine": "Read articles about tech",
"description": "I have been working in tech for long, and here are my thoughts of random stuff"
},
{
"id": "b9e0d686-351d-49af-8e3d-b62023f44dbe",
"name": "Gaming",
"coverImage": "category/b9e0d686-351d-49af-8e3d-b62023f44dbe/media/game.png",
"tagLine": "Read articles about games",
"description": "I like to game, and here are my thoughts on games"
}
]

View File

@ -0,0 +1,38 @@
{
"darkTheme": {
"theme": "Dark Mode",
"background": "bg-dark",
"textColor": "text-white",
"captionColor": "#8a8a8a",
"fontAwesomeIcon": "faSun",
"borderColor": "white",
"categoryNavigator": "light",
"navBar": {
"navBarTheme": "navbar-dark",
"background": "bg-secondary",
"buttonColor": "light"
},
"footer": {
"background": "bg-secondary",
"text": "bg-white"
}
},
"lightTheme":{
"theme": "Light Mode",
"background": "bg-light",
"textColor": "text-black",
"captionColor": "#605f5f",
"fontAwesomeIcon": "faMoon",
"borderColor": "black",
"categoryNavigator": "dark",
"navBar": {
"navBarTheme": "navbar-light",
"background": "bg-secondary",
"buttonColor": "light"
},
"footer": {
"background": "bg-secondary",
"text": "bg-white"
}
}
}

View File

@ -0,0 +1,13 @@
{
"name": "John Doe",
"greetingLine": "Hi! My name is",
"tagLine": "Me like tech. Checkout my blog where I have nice stuff!",
"profilePhoto": "homepage/media/profile.png",
"links": {
"instagram": ""
},
"contact":{
"email":"",
"phone": ""
}
}

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>Hi</h1>
</body>
</html>

View File

@ -3,6 +3,7 @@
"private": true,
"version": "0.0.0",
"type": "module",
"proxy": "http://127.0.0.1:8000",
"scripts": {
"dev:view": "REACT_APP_VIEW_TYPE=view vite --host 0.0.0.0 --port 3000 --mode view",
"dev:editableview": "REACT_APP_VIEW_TYPE=editableview vite --host 0.0.0.0 --port 3000 --mode editableview",

View File

@ -3,7 +3,7 @@
"name": "Blog 1",
"description": "A subtitle for Blog 1",
"tagLine": "Read blog",
"coverImage": "blogs/72e4d550-a19b-4b62-bf5a-13f98813d31a/media/blog1.png",
"coverImage": "blog/72e4d550-a19b-4b62-bf5a-13f98813d31a/media/blog1.png",
"parentCategory": "520b7982-069e-4a48-9ef3-64507d86a579",
"contentBody": "<p>Lorem ipsum dolor <a href='#'>sit amet</a>, consectetur adipiscing elit. Donec non ipsum in nunc pretium gravida id ut lectus. Duis ligula nisl, egestas a tortor nec, scelerisque hendrerit urna. Nullam gravida, ante id aliquet ultrices, justo metus aliquet augue, vestibulum posuere massa lacus at est. Cras vitae dolor euismod, volutpat quam eu, cursus enim. Maecenas in magna ut augue ultrices laoreet. Maecenas sapien sem, mollis sed ipsum nec, viverra vehicula quam. Sed et pulvinar justo. Quisque et vestibulum dui. Aliquam laoreet tempus neque, et eleifend nulla vestibulum eget. Cras tempus justo at nunc facilisis auctor. Duis facilisis tortor eu risus aliquam dapibus nec sit amet est. Maecenas ante lectus, sagittis eu facilisis sit amet, convallis eu ex. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean imperdiet vulputate ipsum sed scelerisque. Donec sit amet rutrum est. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam gravida augue sem, quis aliquet justo varius non. Nam tortor nulla, bibendum sit amet finibus sed, ultricies non tellus. Aliquam condimentum risus ut felis porta iaculis. Nullam gravida mauris lacinia finibus hendrerit. Sed nec consectetur erat, sed tincidunt velit. Donec sit amet nulla at sem blandit imperdiet ut eu nisl. Sed condimentum, lectus quis sodales commodo, arcu turpis sodales ex, ac placerat mi turpis sit amet mi. Nullam lorem velit, porta eu quam eu, hendrerit egestas nibh. Sed non pellentesque arcu. Nam felis lectus, scelerisque in semper a, faucibus sit amet nibh. Cras est ligula, pretium id maximus nec, hendrerit eu ante. Maecenas tincidunt est ante, sed vestibulum mauris dignissim nec. Phasellus varius varius leo in pharetra. Aenean vestibulum id dui cursus lobortis. Mauris vel orci massa. Nullam vitae lorem mattis, lobortis dui in, pharetra velit. Aenean non urna ac felis volutpat consequat sit amet sed nisi. Quisque rutrum nisi ac erat ultricies tempor. Etiam nec pellentesque metus. Nulla tempus mi a ex rutrum, ut luctus tellus porta. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus et quam sit amet arcu mattis commodo quis non risus. Sed luctus non dui ullamcorper consectetur. Duis eros magna, tempus ut aliquam sit amet, tempor vel nibh. Vestibulum hendrerit odio convallis elit pretium dictum. Proin ligula dolor, finibus eget lacus sed, facilisis fringilla lorem. Quisque quis lacus sit amet massa blandit fringilla vel vitae tortor. Vivamus dictum nibh vel justo ullamcorper faucibus. Nullam vitae augue pretium erat semper rhoncus. Etiam tempus, arcu feugiat hendrerit pretium, nisi justo sollicitudin velit, sagittis elementum ante metus sed ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam quis ligula euismod, venenatis arcu vitae, condimentum arcu. Donec vitae nisl aliquam, rutrum arcu vel, sollicitudin justo. Vestibulum nec sagittis massa. Etiam maximus, erat vitae dapibus vulputate, velit lectus imperdiet est, sed lobortis ante erat lobortis arcu. Maecenas mollis nunc ut nisi tristique tempus. Nulla tempor est non dui scelerisque, eget semper augue consequat.</p>"
}

View File

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

@ -3,7 +3,7 @@
"name": "Blog 2",
"description": "A subtitle for Blog 2",
"tagLine": "Read blog",
"coverImage": "blogs/b4d9e1a0-4a77-48eb-a04b-06ec23e2b73e/media/blog2.png",
"coverImage": "blog/b4d9e1a0-4a77-48eb-a04b-06ec23e2b73e/media/blog2.png",
"parentCategory": "520b7982-069e-4a48-9ef3-64507d86a579",
"contentBody": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec non ipsum in nunc pretium gravida id ut lectus. Duis ligula nisl, egestas a tortor nec, scelerisque hendrerit urna. Nullam gravida, ante id aliquet ultrices, justo metus aliquet augue, vestibulum posuere massa lacus at est. Cras vitae dolor euismod, volutpat quam eu, cursus enim. Maecenas in magna ut augue ultrices laoreet. Maecenas sapien sem, mollis sed ipsum nec, viverra vehicula quam. Sed et pulvinar justo. Quisque et vestibulum dui. Aliquam laoreet tempus neque, et eleifend nulla vestibulum eget. Cras tempus justo at nunc facilisis auctor. Duis facilisis tortor eu risus aliquam dapibus nec sit amet est. Maecenas ante lectus, sagittis eu facilisis sit amet, convallis eu ex. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean imperdiet vulputate ipsum sed scelerisque. Donec sit amet rutrum est. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam gravida augue sem, quis aliquet justo varius non. Nam tortor nulla, bibendum sit amet finibus sed, ultricies non tellus. Aliquam condimentum risus ut felis porta iaculis. Nullam gravida mauris lacinia finibus hendrerit. Sed nec consectetur erat, sed tincidunt velit. Donec sit amet nulla at sem blandit imperdiet ut eu nisl. Sed condimentum, lectus quis sodales commodo, arcu turpis sodales ex, ac placerat mi turpis sit amet mi. Nullam lorem velit, porta eu quam eu, hendrerit egestas nibh. Sed non pellentesque arcu. Nam felis lectus, scelerisque in semper a, faucibus sit amet nibh. Cras est ligula, pretium id maximus nec, hendrerit eu ante. Maecenas tincidunt est ante, sed vestibulum mauris dignissim nec. Phasellus varius varius leo in pharetra. Aenean vestibulum id dui cursus lobortis. Mauris vel orci massa. Nullam vitae lorem mattis, lobortis dui in, pharetra velit. Aenean non urna ac felis volutpat consequat sit amet sed nisi. Quisque rutrum nisi ac erat ultricies tempor. Etiam nec pellentesque metus. Nulla tempus mi a ex rutrum, ut luctus tellus porta. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus et quam sit amet arcu mattis commodo quis non risus. Sed luctus non dui ullamcorper consectetur. Duis eros magna, tempus ut aliquam sit amet, tempor vel nibh. Vestibulum hendrerit odio convallis elit pretium dictum. Proin ligula dolor, finibus eget lacus sed, facilisis fringilla lorem. Quisque quis lacus sit amet massa blandit fringilla vel vitae tortor. Vivamus dictum nibh vel justo ullamcorper faucibus. Nullam vitae augue pretium erat semper rhoncus. Etiam tempus, arcu feugiat hendrerit pretium, nisi justo sollicitudin velit, sagittis elementum ante metus sed ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Etiam quis ligula euismod, venenatis arcu vitae, condimentum arcu. Donec vitae nisl aliquam, rutrum arcu vel, sollicitudin justo. Vestibulum nec sagittis massa. Etiam maximus, erat vitae dapibus vulputate, velit lectus imperdiet est, sed lobortis ante erat lobortis arcu. Maecenas mollis nunc ut nisi tristique tempus. Nulla tempor est non dui scelerisque, eget semper augue consequat.</p>"
}

View File

Before

Width:  |  Height:  |  Size: 882 KiB

After

Width:  |  Height:  |  Size: 882 KiB

View File

@ -9,7 +9,7 @@
"id": "72e4d550-a19b-4b62-bf5a-13f98813d31a",
"name": "Blog 1",
"description": "A subtitle for Blog 1",
"coverImage": "blogs/72e4d550-a19b-4b62-bf5a-13f98813d31a/media/blog1.png",
"coverImage": "blog/72e4d550-a19b-4b62-bf5a-13f98813d31a/media/blog1.png",
"tagLine": "Read more",
"parentCategory": "520b7982-069e-4a48-9ef3-64507d86a579"
},
@ -17,7 +17,7 @@
"id": "b4d9e1a0-4a77-48eb-a04b-06ec23e2b73e",
"name": "Blog 2",
"description": "A subtitle for Blog 2",
"coverImage": "blogs/b4d9e1a0-4a77-48eb-a04b-06ec23e2b73e/media/blog2.png",
"coverImage": "blog/b4d9e1a0-4a77-48eb-a04b-06ec23e2b73e/media/blog2.png",
"tagLine": "Read more",
"parentCategory": "520b7982-069e-4a48-9ef3-64507d86a579"
}]

View File

Before

Width:  |  Height:  |  Size: 3.0 MiB

After

Width:  |  Height:  |  Size: 3.0 MiB

View File

Before

Width:  |  Height:  |  Size: 818 KiB

After

Width:  |  Height:  |  Size: 818 KiB

0
frontend/public/data/category/category-metadata.json Normal file → Executable file
View File

0
frontend/public/data/homepage/media/profile.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

10
frontend/public/data/shared/theme-config.json Normal file → Executable file
View File

@ -8,8 +8,8 @@
"linkTextColor": "text-black",
"captionColor": "#8a8a8a",
"fontAwesomeIcon": "faSun",
"borderColor": "white",
"categoryNavigator": "light",
"borderColor": "secondary",
"buttonColor": "light",
"navBar": {
"navBarTheme": "navbar-dark",
"background": "bg-secondary",
@ -17,7 +17,7 @@
},
"footer": {
"background": "bg-secondary",
"text": "bg-white"
"text": "text-white"
}
},
"lightTheme":{
@ -28,8 +28,8 @@
"linkTextColor": "text-white",
"captionColor": "#605f5f",
"fontAwesomeIcon": "faMoon",
"borderColor": "black",
"categoryNavigator": "dark",
"borderColor": "secondary",
"buttonColor": "dark",
"navBar": {
"navBarTheme": "navbar-light",
"background": "bg-secondary",

0
frontend/public/data/shared/user-data.json Normal file → Executable file
View File

0
frontend/public/vite.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

0
frontend/src/App.css Normal file → Executable file
View File

2
frontend/src/App.jsx Normal file → Executable file
View File

@ -53,8 +53,8 @@ function App() {
<div className="app-container">
<Router>
<Header className="header" ThemeSwitcher={themeSwitcher} GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} />
<Notification isOpen={isOpen} message={notificationMessage} />
<div className={`p-0 ${themeConfig[globalTheme].background}`}>
<Notification isOpen={isOpen} message={notificationMessage} />
<Routes>
<Route path="/" element={<Home GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} />} />
<Route path="/categories" element={<CategoryList notificationToggler={notificationToggler} GlobalTheme={globalTheme} ThemeConfig={themeConfig} />} />

59
frontend/src/AppEditable.jsx Normal file → Executable file
View File

@ -16,7 +16,7 @@ import Footer from './components/editable/shared/footer';
import Notification from './components/editable/shared/notification';
//Import Services
import DataService from './services/data-service'
import EditableDataService from './services/editable-data-service'
function AppEditable() {
const [userData, setUserData] = useState(null);
@ -25,44 +25,69 @@ function AppEditable() {
const [isOpen, setIsOpen] = useState(false);
const [notificationMessage, setNotificationMessage] = useState("")
const notificationToggler = (message) => {
const notificationToggler = (message, color) => {
setIsOpen(true)
setNotificationMessage(message)
setNotificationMessage({message: message, color: color})
setTimeout(() => {
setIsOpen(false)
}, 3500)
}, 1500)
}
const setInfo = async (path, data) => {
try {
const response = await EditableDataService.updateData(path, data);
setConfigData();
return response.status;
} catch (error) {
return error.response ? error.response.status : 500;
}
}
const setConfigData = () => {
EditableDataService.getData('/data/shared/user-data/').then( response => {
let responseData = response.data[0]
setUserData({
"name": responseData["name"],
"introContent": responseData["intro_content"],
"profilePhoto": responseData["profile_photo"]
})
}
)
EditableDataService.getData('/data/shared/theme-config/').then( response =>{
let responseData = response.data[0]
setThemeConfig({
"defaultTheme": responseData["default_theme"],
"darkTheme": JSON.parse(responseData["dark_theme"]),
"lightTheme": JSON.parse(responseData["light_theme"])
})
setGlobalTheme(responseData["default_theme"])
}
)
}
useEffect(() => {
DataService.getData('shared/user-data').then( response =>
setUserData(response.data)
)
DataService.getData('shared/theme-config').then( response =>{
setThemeConfig(response.data)
setGlobalTheme(response.data.defaultTheme)
}
)
setConfigData()
},[])
const themeSwitcher = (theme) => {
setGlobalTheme(theme);
}
if (themeConfig)
if (themeConfig && userData && globalTheme)
return (
<div className="app-container">
<Router>
<Header className="header" ThemeSwitcher={themeSwitcher} GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} />
<Notification isOpen={isOpen} message={notificationMessage} />
<Header className="header" ThemeSwitcher={themeSwitcher} GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} notificationToggler={notificationToggler} setInfo={setInfo} />
<div className={`p-0 ${themeConfig[globalTheme].background}`}>
<Routes>
<Route path="/" element={<Home GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} />} />
<Route path="/" element={<Home notificationToggler={notificationToggler} GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} setInfo={setInfo} />} />
<Route path="/categories" element={<CategoryList notificationToggler={notificationToggler} GlobalTheme={globalTheme} ThemeConfig={themeConfig} />} />
<Route path="/categories/:categoryID" element={<BlogList notificationToggler={notificationToggler} GlobalTheme={globalTheme} ThemeConfig={themeConfig} />} />
<Route path="/blog/:blogID" element={<Blog notificationToggler={notificationToggler} GlobalTheme={globalTheme} ThemeConfig={themeConfig} />} />
</Routes>
</div>
<Footer className="footer" ThemeSwitcher={themeSwitcher} GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} />
<Footer notificationToggler={notificationToggler} setInfo={setInfo} className="footer" ThemeSwitcher={themeSwitcher} GlobalTheme={globalTheme} ThemeConfig={themeConfig} UserData={userData} />
<Notification style={{width: '20%'}} className="fixed-top" isOpen={isOpen} message={notificationMessage} />
</Router>
</div>
);

0
frontend/src/assets/react.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

33
frontend/src/components/editable/blog-list.jsx Normal file → Executable file
View File

@ -14,9 +14,7 @@ import {
CardImg,
CardTitle,
CardText,
CardBody,
Button,
ButtonGroup
CardBody
} from 'reactstrap';
import { Link, useParams } from 'react-router-dom';
@ -34,8 +32,9 @@ function BlogList(props) {
useEffect(() => {
DataService.getData(`category/${categoryID}/category-data`).then(response =>{
setCategoryData(response.data);
console.log(response.data)
if (response.data.featuredBlog){
DataService.getData(`blogs/${response.data.featuredBlog}/blog-data`).then(response =>
DataService.getData(`blog/${response.data.featuredBlog}/blog-data`).then(response =>
setFeaturedBlogData(response.data)
);
}
@ -52,36 +51,46 @@ function BlogList(props) {
<Row className="justify-content-center align-items-center">
<Col className="d-flex flex-column align-items-center">
<div className="w-100">
<Card className={`my-2 ${ThemeConfig[GlobalTheme].background}`} style={{"width": "100%"}}>
<Card className={`my-2 ${ThemeConfig[GlobalTheme].background}`} style={{width: "100%", border: "none"}}>
<CardBody>
<CardTitle style={{ display: "grid" }} className={`${ThemeConfig[GlobalTheme].textColor} justify-content-center`} tag="h1">
{`Blogs in ${categoryData.name}`}<Button className='mt-2' outline>Add New</Button>
{`Blogs in ${categoryData.name}`}
</CardTitle>
</CardBody>
</Card>
</div>
<div className="" style={{ width: '70%', margin: 'auto', display: 'flex', flexWrap: 'wrap', gap: '1rem' }}>
<h3 className={`${ThemeConfig[GlobalTheme].textColor}`}>
{`All blogs`}
{`Featured`}
</h3>
{
featuredBlogData === 'loading' ? <Spinner /> :
<CardListViewer
key={featuredBlogData.id}
totalItems={featuredBlogData === 'nodata' ? 0 : 1}
cardType={"longCard"}
resourceType={"blog"}
textColor={ThemeConfig[GlobalTheme].textColor}
bgColor={ThemeConfig[GlobalTheme].background}
borderColor={ThemeConfig[GlobalTheme].borderColor}
itemObject={featuredBlogData}
/>
}
{
categoryData === 'loading' ? <Spinner /> :
categoryData.blogMetadata.map((item, index) => (
<CardListViewer
key={item.id}
totalItems={categoryData.blogMetadata.length}
cardType={"longCard"}
cardType={"smallCard"}
resourceType={"blog"}
textColor={ThemeConfig[GlobalTheme].textColor}
bgColor={ThemeConfig[GlobalTheme].background}
borderColor={ThemeConfig[GlobalTheme].borderColor}
itemObject={item}
/>
))
}
<ButtonGroup>
<Button outline>Save Data</Button>
<Button outline>Publish Data</Button>
</ButtonGroup>
</div>
</Col>
</Row>

133
frontend/src/components/editable/blog.jsx Normal file → Executable file
View File

@ -1,5 +1,4 @@
import { useEffect, useState } from 'react';
import parse from 'html-react-parser';
import { useEffect, useState, useRef } from 'react';
import DataService from '../../services/data-service';
import MediaService from '../../services/media-service'
@ -7,53 +6,42 @@ import CategoryBar from './shared/category-bar';
import EditorComponent from './shared/tiptap';
import {
Container,Row, Col,Spinner, UncontrolledCollapse, Button, ButtonGroup, Card, CardBody
Container,Row, Col,Spinner, UncontrolledCollapse, Button, ButtonGroup, Card, CardBody, Input, InputGroup, InputGroupText
} from 'reactstrap';
import { Link, useParams } from 'react-router-dom';
function Blog(props) {
const nameField = useRef(null);
const descriptionField = useRef(null);
const tagLineField = useRef(null);
const { blogID } = useParams();
const GlobalTheme = props.GlobalTheme;
const ThemeConfig = props.ThemeConfig;
const [blogData, setBlogData] = useState([]);
const [blogContent, setBlogContent] = useState()
const [blogContent, setBlogContent] = useState();
const replace = (node) => {
if (node.type === 'tag') {
if (node.name === 'a') {
const newClasses = `${ThemeConfig[GlobalTheme].linkBackground} ${ThemeConfig[GlobalTheme].linkTextColor}`;
const existingClasses = node.attribs.class ? `${node.attribs.class} ` : '';
node.attribs.class = `${existingClasses}${newClasses}`;
node.attribs.rel = 'noopener noreferrer';
node.attribs.target = '_blank';
const setInfo = (event) => {
let localEditedBlogData = {...blogData};
localEditedBlogData["name"] = nameField.current.value;
localEditedBlogData["description"] = descriptionField.current.value;
localEditedBlogData["tagLine"] = tagLineField.current.value;
localEditedBlogData["contentBody"] = blogContent
setBlogData(localEditedBlogData);
props.notificationToggler('Data saved!');
}
if (node.name === 'img') {
const newClasses = `img-fluid mt-2 mb-2 rounded`;
const existingClasses = node.attribs.class ? `${node.attribs.class} ` : '';
node.attribs.class = `${existingClasses}${newClasses}`;
}
}
};
useEffect(() => {
DataService.getData(`blogs/${blogID}/blog-data`).then(response =>{
DataService.getData(`blog/${blogID}/blog-data`).then(response =>{
setBlogData(response.data)
const parsedContent = parse(response.data.contentBody, { replace });
setBlogContent(parsedContent);
setBlogContent(response.data.contentBody)
}
);
}, []);
useEffect(() => {
if (blogData.contentBody){
const parsedContent = parse(blogData.contentBody, { replace });
setBlogContent(parsedContent);
}
}, [GlobalTheme])
if (GlobalTheme && ThemeConfig && blogData) {
return (
<Container fluid className={`${ThemeConfig[GlobalTheme].background}`}>
@ -70,68 +58,24 @@ function Blog(props) {
<Row className="mr-2 ml-2 mb-2 mt-1 blogContent">
<Col xs="3" className="d-none d-md-block"></Col>
<Col xs={`${window.screen.width >= 765 ? '6':''}`}>
<h1 className={`${ThemeConfig[GlobalTheme].textColor}`}>{blogData.name}</h1>
<h4 className={`${ThemeConfig[GlobalTheme].textColor}`}>{blogData.description}</h4>
<div>
<Button
color="primary"
id="toggler"
style={{
marginBottom: '1rem'
}}
>
Share
</Button>
<UncontrolledCollapse toggler="#toggler">
<Card style={{overflowX: 'auto'}}>
<CardBody>
<ButtonGroup
vertical
className="my-2"
>
<Button outline>
<Link className="p-3" to="#" onClick={(e) => {
e.preventDefault();
navigator.clipboard.writeText(window.location.href).then(() => {
props.notificationToggler("Link copied")
})
return false;
}}>
Copy Link
</Link>
</Button>
<Button outline>
<Link className="p-3" to="#" onClick={(e) => {
e.preventDefault();
window.open(`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(window.location.href)}`, 'facebook-share-dialog', 'width=800,height=600');
return false;
}}>
Facebook
</Link>
</Button>
<Button outline>
<Link className="p-3" to="#" onClick={(e) => {
e.preventDefault();
window.open(`https://www.reddit.com/submit?url=${window.location.href}&title=${blogData.name}`, 'facebook-share-dialog', 'width=800,height=600');
return false;
}}>
Reddit
</Link>
</Button>
<Button outline>
<Link className="p-3" to="#" onClick={(e) => {
e.preventDefault();
window.open(`https://twitter.com/intent/tweet?text=Check%20out%20this%20article!&url=${window.location.href}`, 'facebook-share-dialog', 'width=800,height=600');
return false;
}}>
X
</Link>
</Button>
</ButtonGroup>
</CardBody>
</Card>
</UncontrolledCollapse>
</div>
<InputGroup className="mb-3">
<InputGroupText>
Name
</InputGroupText>
<Input innerRef={nameField} defaultValue={blogData.name} />
</InputGroup>
<InputGroup className="mb-3">
<InputGroupText>
Description
</InputGroupText>
<Input innerRef={descriptionField} defaultValue={blogData.description} />
</InputGroup>
<InputGroup>
<InputGroupText>
Tagline
</InputGroupText>
<Input innerRef={tagLineField} defaultValue={blogData.tagLine} />
</InputGroup>
</Col>
<Col xs="3" className="d-none d-md-block"></Col>
</Row>
@ -141,15 +85,14 @@ function Blog(props) {
<hr style={{"borderColor": `${ThemeConfig[GlobalTheme].borderColor}`}} />
</Col>
</Row>
<Row className="mr-2 ml-2 mt-1">
<Col xs="3" className="d-none d-md-block"></Col>
<Col className={`blogContent ${ThemeConfig[GlobalTheme].textColor}`} style={{marginBottom: '25px'}}>
<EditorComponent content={blogData.contentBody}/>
<EditorComponent setContent={setBlogContent} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig} content={blogData.contentBody}/>
<ButtonGroup className='mt-4'>
<Button outline>Save Data</Button>
<Button outline>Publish Data</Button>
<Button onClick={(event) => setInfo(event)} color={ThemeConfig[GlobalTheme].buttonColor} outline>Save Data</Button>
<Button color={ThemeConfig[GlobalTheme].buttonColor} outline>Publish Data</Button>
</ButtonGroup>
</Col>

10
frontend/src/components/editable/category-list.jsx Normal file → Executable file
View File

@ -37,13 +37,12 @@ function Blogs(props) {
return (
<Container fluid className={`p-0 mb-2 ${ThemeConfig[GlobalTheme].background}`}>
<Row className="justify-content-center align-items-center">
<Col className="d-flex flex-column align-items-center">
<div className="w-100">
<Card className={`my-2 ${ThemeConfig[GlobalTheme].background}`} style={{"width": "100%"}}>
<Card className={`my-2 ${ThemeConfig[GlobalTheme].background}`} style={{width: "100%", border: "none"}}>
<CardBody>
<CardTitle style={{ display: "grid" }} className={`${ThemeConfig[GlobalTheme].textColor} justify-content-center`} tag="h1">
{"Categories"}<Button className='mt-2' outline>Add New</Button>
{"Categories"}<Button className='mt-2' color={ThemeConfig[GlobalTheme].buttonColor} outline>Add New</Button>
</CardTitle>
</CardBody>
</Card>
@ -60,12 +59,13 @@ function Blogs(props) {
resourceType={"categories"}
textColor={ThemeConfig[GlobalTheme].textColor}
bgColor={ThemeConfig[GlobalTheme].background}
borderColor={ThemeConfig[GlobalTheme].borderColor}
itemObject={item}
/>
)) : <Spinner />}
<ButtonGroup className='mt-4'>
<Button outline>Save Data</Button>
<Button outline>Publish Data</Button>
<Button color={ThemeConfig[GlobalTheme].buttonColor} outline>Save Data</Button>
<Button color={ThemeConfig[GlobalTheme].buttonColor} outline>Publish Data</Button>
</ButtonGroup>
</div>
</Col>

32
frontend/src/components/editable/home.jsx Normal file → Executable file
View File

@ -1,11 +1,33 @@
import { Container, Spinner, Input, InputGroup, InputGroupText, Button, ButtonGroup } from 'reactstrap';
import {useEffect, useState, useRef} from 'react';
import EditorComponent from './shared/tiptap';
import MediaService from '../../services/media-service'
function HomePage(props) {
const [introContent, setIntroContent] = useState("")
const [saveKeyReady, setSaveKeyReady] = useState(true)
const nameField = useRef(null)
const UserData = props.UserData ? props.UserData : <Spinner> Loading... </Spinner>
const GlobalTheme = props.GlobalTheme;
const ThemeConfig = props.ThemeConfig;
const setInfo = async () => {
let response = await props.setInfo('/data/shared/update/user-data/', {
"name": nameField.current.value,
"intro_content": introContent,
"profile_photo": ""
})
console.log(response)
if (response === 200)
props.notificationToggler("Data saved successfully!")
if ([500, 404, 403].includes(response))
props.notificationToggler("Something failed!", "danger")
}
useEffect(() => {
setIntroContent(UserData.introContent)
}, [UserData])
if (GlobalTheme && ThemeConfig)
return (
<Container fluid className={`p-0 mt-5 ${ThemeConfig[GlobalTheme].background}`}>
@ -17,13 +39,13 @@ function HomePage(props) {
<InputGroupText>
Name
</InputGroupText>
<Input defaultValue={UserData.name} />
<Input innerRef={nameField} defaultValue={UserData.name} />
</InputGroup>
<EditorComponent content={UserData.introContent}/>
<EditorComponent GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig} content={UserData.introContent} setContent={setIntroContent}/>
</>
<ButtonGroup className='mt-4'>
<Button outline>Save Data</Button>
<Button outline>Publish Data</Button>
<ButtonGroup className={`mt-4`}>
<Button onClick={() => setInfo()} color={ThemeConfig[GlobalTheme].buttonColor} outline>Save Data</Button>
<Button color={ThemeConfig[GlobalTheme].buttonColor} outline>Publish Data</Button>
</ButtonGroup>
</div>
</div>

View File

@ -13,15 +13,15 @@ import { Link } from 'react-router-dom';
function CardListViewer(props) {
const itemObject = props.itemObject
if (props.totalItems > 0 && itemObject && Object.keys(itemObject).length !== 0)
if (props.totalItems > 0 && itemObject && Object.keys(itemObject).length !== 0){
if (props.resourceType === 'categories')
return (
<Card className={`my-2 ${props.bgColor}`} style={{"width": props.cardType === "smallCard" ? "18rem": "100%"}}>
<Card color={props.borderColor} outline className={`my-2 ${props.bgColor}`} style={{"width": props.cardType === "smallCard" ? "18rem": "100%"}}>
{itemObject.coverImage !== "" ? <CardImg src={MediaService.getMedia(itemObject.coverImage)} style={{ "height": "180px", "objectFit": "cover" }} top width="100%" /> : ""}
<CardBody>
<CardTitle className={`${props.textColor}`} tag="h5">
<CardTitle className={`mb-3 ${props.textColor}`} tag="h5">
<InputGroup>
<InputGroupText>
Name
@ -55,8 +55,30 @@ function CardListViewer(props) {
</CardBody>
</Card>
)
else
else
return (
<Card color={props.borderColor} outline className={`my-2 ${props.bgColor}`} style={{"width": props.cardType === "smallCard" ? "18rem": "100%"}}>
{itemObject.coverImage !== "" ? <CardImg src={MediaService.getMedia(itemObject.coverImage)} style={{ "height": "180px", "objectFit": "cover" }} top width="100%" /> : ""}
<CardBody>
<Link to={`/${props.resourceType}/${itemObject.id}`}>
<CardTitle className={`${props.textColor}`} tag="h5">
{itemObject.name}
</CardTitle>
<CardText className={`${props.textColor}`}>
{itemObject.description}
</CardText>
<CardText>
<small className={`${props.textColor}`}>
{itemObject.tagLine}
</small>
</CardText>
</Link>
</CardBody>
</Card>
)
}
else
return(<h3 className={`${props.textColor}`}>No items found in this section</h3>)
}
}
export default CardListViewer

View File

@ -34,10 +34,9 @@ function CategoryBar(props) {
<Button
key={item.id}
className="btn-lg"
color={`${ThemeConfig[GlobalTheme].categoryNavigator}`}
color={`${ThemeConfig[GlobalTheme].buttonColor}`}
outline
active={props.currentPage === item.id}
>
<Link className="p-3" to={`/categories/${item.id}`}>
{item.name}

62
frontend/src/components/editable/shared/footer.jsx Normal file → Executable file
View File

@ -6,18 +6,76 @@ import {
Col,
Nav,
NavLink,
Spinner
Spinner,
Button,
ButtonGroup
} from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSun, faMoon, faPen, faBrush } from '@fortawesome/free-solid-svg-icons';
const Footer = (props) => {
const GlobalTheme = props.GlobalTheme;
const ThemeConfig = props.ThemeConfig;
const UserData = props.UserData;
const setInfo = async (color, colorArea) => {
let localThemeConfig = {...ThemeConfig}
localThemeConfig[GlobalTheme].footer[colorArea] = `${color}`
let response = await props.setInfo('/data/shared/update/theme-config/', GlobalTheme === "darkTheme" ? {
"dark_theme": JSON.stringify(localThemeConfig[GlobalTheme]),
}:{
"light_theme": JSON.stringify(localThemeConfig[GlobalTheme]),
})
if (response === 200)
props.notificationToggler(`Color set for ${ThemeConfig[GlobalTheme].theme} successfully!`)
if ([500, 404, 403].includes(response))
props.notificationToggler("Something failed!", "danger")
}
return (
<footer className={`footer p-4 text-white ${ThemeConfig ? ThemeConfig[GlobalTheme].footer['background'] : ""}`} id="site-footer">
<footer className={`footer p-4 ${ThemeConfig ? ThemeConfig[GlobalTheme].footer['background'] + ' ' + ThemeConfig[GlobalTheme].footer['text'] : ""}`} id="site-footer">
<Container className='p-1'>
<Row>
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px', marginRight: '15px'}}>
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}>
<FontAwesomeIcon icon={faBrush} /> Set color
</Button>
<Button
color='primary'
onClick={() => setInfo('bg-primary', 'background')}/>
<Button
color='secondary'
onClick={() => setInfo('bg-secondary', 'background')}/>
<Button
color='success'
onClick={() => setInfo('bg-success', 'background')}/>
<Button
color='danger'
onClick={() => setInfo('bg-danger', 'background')}/>
<Button
color='warning'
onClick={() => setInfo('bg-warning', 'background')}/>
<Button
color='info'
onClick={() => setInfo('bg-info', 'background')}/>
<Button
color='light'
onClick={() => setInfo('bg-light', 'background')}/>
<Button
color='dark'
onClick={() => setInfo('bg-dark', 'background')}/>
</ButtonGroup>
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}>
<FontAwesomeIcon icon={faBrush} /> Set footer text color
</Button>
<Button
color='dark'
onClick={() => setInfo('text-light', 'text')}>White on black</Button>
<Button
color='light'
onClick={() => setInfo('text-black', 'text')}>Black on white</Button>
</ButtonGroup>
<Col md="12">
<div className="blogContent text-center text-md-left mt-3">
{new Date().getFullYear()}, <a className={`${ThemeConfig[GlobalTheme].linkBackground} ${ThemeConfig[GlobalTheme].linkTextColor}`} href="/">{ UserData ? UserData.name : <Spinner> Loading... </Spinner> }</a>

61
frontend/src/components/editable/shared/navbar.jsx Normal file → Executable file
View File

@ -15,7 +15,7 @@ import {
import { useState, useEffect } from 'react';
import MediaService from '../../../services/media-service'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSun, faMoon, faPen } from '@fortawesome/free-solid-svg-icons';
import { faSun, faMoon, faPen, faBrush } from '@fortawesome/free-solid-svg-icons';
import { Link } from 'react-router-dom';
function Header(props) {
@ -27,6 +27,20 @@ function Header(props) {
const [collapseClasses, setCollapseClasses] = useState('');
const [themeSelected, setThemeSelected] = useState('lightTheme');
const setInfo = async (color, colorArea) => {
let localThemeConfig = {...ThemeConfig}
localThemeConfig[GlobalTheme].navBar[colorArea] = `${color}`
let response = await props.setInfo('/data/shared/update/theme-config/', GlobalTheme === "darkTheme" ? {
"dark_theme": JSON.stringify(localThemeConfig[GlobalTheme]),
}:{
"light_theme": JSON.stringify(localThemeConfig[GlobalTheme]),
})
if (response === 200)
props.notificationToggler(`Color set for ${ThemeConfig[GlobalTheme].theme} successfully!`)
if ([500, 404, 403].includes(response))
props.notificationToggler("Something failed!", "danger")
}
useEffect(() => {
props.ThemeSwitcher(themeSelected)
}, [themeSelected])
@ -58,7 +72,7 @@ function Header(props) {
</NavbarBrand>
<Nav className="ml-lg-auto" navbar>
<NavItem>
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px', marginRight: '15px'}}>
<Button color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}>
<Link to="/categories">
<FontAwesomeIcon icon={faPen} /> Blogs
@ -81,6 +95,49 @@ function Header(props) {
<FontAwesomeIcon icon={faMoon}/> Dark Theme
</Button>
</ButtonGroup>
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px', marginRight: '15px'}}>
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}>
<FontAwesomeIcon icon={faBrush} /> Set color
</Button>
<Button
color='primary'
onClick={() => setInfo('bg-primary', 'background')}/>
<Button
color='secondary'
onClick={() => setInfo('bg-secondary', 'background')}/>
<Button
color='success'
onClick={() => setInfo('bg-success', 'background')}/>
<Button
color='danger'
onClick={() => setInfo('bg-danger', 'background')}/>
<Button
color='warning'
onClick={() => setInfo('bg-warning', 'background')}/>
<Button
color='info'
onClick={() => setInfo('bg-info', 'background')}/>
<Button
color='light'
onClick={() => setInfo('bg-light', 'background')}/>
<Button
color='dark'
onClick={() => setInfo('bg-dark', 'background')}/>
</ButtonGroup>
<ButtonGroup style={{marginTop: '15px', marginBottom: '15px'}}>
<Button disabled color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}>
<FontAwesomeIcon icon={faBrush} /> Set button color
</Button>
<Button
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}
outline
onClick={() => setInfo('light', 'buttonColor')}>White</Button>
<Button
color={`${ThemeConfig ? ThemeConfig[GlobalTheme].navBar['buttonColor'] : ""}`}
outline
onClick={() => setInfo('black', 'buttonColor')}>Black</Button>
</ButtonGroup>
</NavItem>
</Nav>
</Container>

View File

@ -7,7 +7,7 @@ function Notification(props) {
<Collapse isOpen={props.isOpen} {...props}>
<Card>
<CardBody>
<Alert>{props.message}</Alert>
<Alert color={props.message.color}>{props.message.message}</Alert>
</CardBody>
</Card>
</Collapse>

70
frontend/src/components/editable/shared/tiptap.jsx Normal file → Executable file
View File

@ -1,4 +1,4 @@
import React, { useCallback } from 'react'
import React, { useCallback, useEffect } from 'react'
import {
Button, ButtonGroup, Label, Input } from 'reactstrap';
import { Color } from '@tiptap/extension-color'
@ -17,15 +17,30 @@ import { faBold, faItalic,
faAlignCenter, faAlignRight,
faAlignJustify, faHighlighter,
faStrikethrough, faCode,
faParagraph, faListUl,
faListUl, faLink,
faListOl, faQuoteLeft,
faQuoteRight, faRulerHorizontal,
faRotateLeft, faRotateRight,
faBars, faLink } from '@fortawesome/free-solid-svg-icons';
faRotateLeft, faRotateRight } from '@fortawesome/free-solid-svg-icons';
const MenuBar = (props) => {
const { editor } = useCurrentEditor()
useEffect(() => {
if (editor){
const handleChange = () => {
props.setContent(editor.getHTML())
}
editor.on('update', handleChange)
return () => {
editor.on('update', handleChange)
}
}
},[editor])
const GlobalTheme = props.GlobalTheme;
const ThemeConfig = props.ThemeConfig;
const setLink = useCallback(() => {
const previousUrl = editor.getAttributes('link').href
const url = window.prompt('URL', previousUrl)
@ -54,8 +69,9 @@ const MenuBar = (props) => {
return (
<>
<ButtonGroup>
<ButtonGroup className='mt-2'>
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().setTextAlign('left').run()}
outline
active={editor.isActive('left')}
@ -63,6 +79,7 @@ const MenuBar = (props) => {
<FontAwesomeIcon icon={faAlignLeft}/>
</Button>
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().setTextAlign('center').run()}
outline
active={editor.isActive('center')}
@ -70,6 +87,7 @@ const MenuBar = (props) => {
<FontAwesomeIcon icon={faAlignCenter}/>
</Button>
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().setTextAlign('right').run()}
outline
active={editor.isActive('right')}
@ -77,15 +95,17 @@ const MenuBar = (props) => {
<FontAwesomeIcon icon={faAlignRight}/>
</Button>
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().setTextAlign('justify').run()}
outline
active={editor.isActive('justify')}
>
<FontAwesomeIcon icon={faAlignJustify}/>
</Button>
</ButtonGroup>
<ButtonGroup style={{marginLeft: '10px'}}>
</ButtonGroup >
<ButtonGroup className='mt-2' style={{marginLeft: '10px'}}>
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().toggleBold().run()}
disabled={
!editor.can()
@ -100,6 +120,7 @@ const MenuBar = (props) => {
<FontAwesomeIcon icon={faBold}/>
</Button>
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().toggleItalic().run()}
disabled={
!editor.can()
@ -114,6 +135,7 @@ const MenuBar = (props) => {
<FontAwesomeIcon icon={faItalic}/>
</Button>
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().toggleUnderline().run()}
outline
active={editor.isActive('underline')}
@ -121,6 +143,7 @@ const MenuBar = (props) => {
<FontAwesomeIcon icon={faUnderline}/>
</Button>
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().toggleHighlight().run()}
outline
active={editor.isActive('highlight')}
@ -128,6 +151,7 @@ const MenuBar = (props) => {
<FontAwesomeIcon icon={faHighlighter}/>
</Button>
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().toggleStrike().run()}
disabled={
!editor.can()
@ -143,6 +167,8 @@ const MenuBar = (props) => {
</Button>
</ButtonGroup>
<Button
className='mt-2'
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={setLink}
style={{marginLeft: '10px'}}
outline
@ -151,6 +177,8 @@ const MenuBar = (props) => {
<FontAwesomeIcon icon={faLink}/>
</Button>
<Button
className='mt-2'
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
style={{marginLeft: '10px'}}
outline
@ -158,8 +186,9 @@ const MenuBar = (props) => {
>
<FontAwesomeIcon icon={faCode}/>
</Button>
<ButtonGroup style={{marginLeft: '10px'}}>
<ButtonGroup className='mt-2' style={{marginLeft: '10px'}}>
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().setParagraph().run()}
outline
active={editor.isActive('paragraph')}
@ -167,6 +196,7 @@ const MenuBar = (props) => {
p
</Button>
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
outline
active={editor.isActive('heading', { level: 1 })}
@ -174,6 +204,7 @@ const MenuBar = (props) => {
h1
</Button>
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
outline
active={editor.isActive('heading', { level: 2 })}
@ -181,6 +212,7 @@ const MenuBar = (props) => {
h2
</Button>
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
outline
active={editor.isActive('heading', { level: 3 })}
@ -188,6 +220,7 @@ const MenuBar = (props) => {
h3
</Button>
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()}
outline
active={editor.isActive('heading', { level: 4 })}
@ -195,6 +228,7 @@ const MenuBar = (props) => {
h4
</Button>
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()}
outline
active={editor.isActive('heading', { level: 5 })}
@ -202,6 +236,7 @@ const MenuBar = (props) => {
h5
</Button>
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()}
outline
active={editor.isActive('heading', { level: 6 })}
@ -209,8 +244,9 @@ const MenuBar = (props) => {
h6
</Button>
</ButtonGroup>
<ButtonGroup style={{marginLeft: '10px'}}>
<ButtonGroup className='mt-2' style={{marginLeft: '10px'}}>
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().toggleBulletList().run()}
outline
active={editor.isActive('bulletList')}
@ -218,6 +254,7 @@ const MenuBar = (props) => {
<FontAwesomeIcon icon={faListUl}/>
</Button>
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().toggleOrderedList().run()}
outline
active={editor.isActive('orderedList')}
@ -226,6 +263,8 @@ const MenuBar = (props) => {
</Button>
</ButtonGroup>
<Button
className='mt-2'
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().toggleBlockquote().run()}
style={{marginLeft: '10px'}}
outline
@ -234,14 +273,17 @@ const MenuBar = (props) => {
<FontAwesomeIcon icon={faQuoteLeft}/> <FontAwesomeIcon icon={faQuoteRight}/>
</Button>
<Button
className='mt-2'
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().setHorizontalRule().run()}
outline
style={{marginLeft: '10px'}}
>
<FontAwesomeIcon icon={faRulerHorizontal}/>
</Button>
<ButtonGroup style={{marginLeft: '10px'}}>
<ButtonGroup className='mt-2' style={{marginLeft: '10px'}}>
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().undo().run()}
disabled={
!editor.can()
@ -254,6 +296,7 @@ const MenuBar = (props) => {
<FontAwesomeIcon icon={faRotateLeft}/>
</Button>
<Button
color={ThemeConfig[GlobalTheme].buttonColor}
onClick={() => editor.chain().focus().redo().run()}
disabled={
!editor.can()
@ -296,8 +339,11 @@ const extensions = [
]
export default (props) => {
if (props.content)
const GlobalTheme = props.GlobalTheme;
const ThemeConfig = props.ThemeConfig;
if (props.content && GlobalTheme && ThemeConfig)
return (
<EditorProvider slotBefore={<MenuBar />} extensions={extensions} content={props.content}></EditorProvider>
<EditorProvider slotBefore={<MenuBar setContent={props.setContent} GlobalTheme={GlobalTheme} ThemeConfig={ThemeConfig}/>} extensions={extensions} content={props.content}></EditorProvider>
)
}

7
frontend/src/components/viewable/blog-list.jsx Normal file → Executable file
View File

@ -32,8 +32,9 @@ function BlogList(props) {
useEffect(() => {
DataService.getData(`category/${categoryID}/category-data`).then(response =>{
setCategoryData(response.data);
console.log(response.data)
if (response.data.featuredBlog){
DataService.getData(`blogs/${response.data.featuredBlog}/blog-data`).then(response =>
DataService.getData(`blog/${response.data.featuredBlog}/blog-data`).then(response =>
setFeaturedBlogData(response.data)
);
}
@ -50,7 +51,7 @@ function BlogList(props) {
<Row className="justify-content-center align-items-center">
<Col className="d-flex flex-column align-items-center">
<div className="w-100">
<Card className={`my-2 ${ThemeConfig[GlobalTheme].background}`} style={{"width": "100%"}}>
<Card className={`my-2 ${ThemeConfig[GlobalTheme].background}`} style={{width: "100%", border: "none"}}>
<CardBody>
<CardTitle style={{ display: "grid" }} className={`${ThemeConfig[GlobalTheme].textColor} justify-content-center`} tag="h1">
{`Blogs in ${categoryData.name}`}
@ -71,6 +72,7 @@ function BlogList(props) {
resourceType={"blog"}
textColor={ThemeConfig[GlobalTheme].textColor}
bgColor={ThemeConfig[GlobalTheme].background}
borderColor={ThemeConfig[GlobalTheme].borderColor}
itemObject={featuredBlogData}
/>
}
@ -84,6 +86,7 @@ function BlogList(props) {
resourceType={"blog"}
textColor={ThemeConfig[GlobalTheme].textColor}
bgColor={ThemeConfig[GlobalTheme].background}
borderColor={ThemeConfig[GlobalTheme].borderColor}
itemObject={item}
/>
))

2
frontend/src/components/viewable/blog.jsx Normal file → Executable file
View File

@ -38,7 +38,7 @@ function Blog(props) {
};
useEffect(() => {
DataService.getData(`blogs/${blogID}/blog-data`).then(response =>{
DataService.getData(`blog/${blogID}/blog-data`).then(response =>{
setBlogData(response.data)
const parsedContent = parse(response.data.contentBody, { replace });
setBlogContent(parsedContent);

3
frontend/src/components/viewable/category-list.jsx Normal file → Executable file
View File

@ -38,7 +38,7 @@ function Blogs(props) {
<Col className="d-flex flex-column align-items-center">
{/* Top Section - Categories */}
<div className="w-100">
<Card className={`my-2 ${ThemeConfig[GlobalTheme].background}`} style={{"width": "100%"}}>
<Card className={`my-2 ${ThemeConfig[GlobalTheme].background}`} style={{width: "100%", border: "none"}}>
<CardBody>
<CardTitle style={{ display: "grid" }} className={`${ThemeConfig[GlobalTheme].textColor} justify-content-center`} tag="h1">
{"Categories"}
@ -57,6 +57,7 @@ function Blogs(props) {
resourceType={"categories"}
textColor={ThemeConfig[GlobalTheme].textColor}
bgColor={ThemeConfig[GlobalTheme].background}
borderColor={ThemeConfig[GlobalTheme].borderColor}
itemObject={item}
/>
)) : <Spinner />}

0
frontend/src/components/viewable/home.jsx Normal file → Executable file
View File

View File

@ -14,10 +14,11 @@ function CardListViewer(props) {
const itemObject = props.itemObject
console.log(itemObject)
if (props.totalItems > 0 && itemObject && Object.keys(itemObject).length !== 0)
return (
<Card className={`my-2 ${props.bgColor}`} style={{"width": props.cardType === "smallCard" ? "18rem": "100%"}}>
<Card color={props.borderColor} outline className={`my-2 ${props.bgColor}`} style={{"width": props.cardType === "smallCard" ? "18rem": "100%"}}>
{itemObject.coverImage !== "" ? <CardImg src={MediaService.getMedia(itemObject.coverImage)} style={{ "height": "180px", "objectFit": "cover" }} top width="100%" /> : ""}
<CardBody>
<Link to={`/${props.resourceType}/${itemObject.id}`}>

View File

@ -34,10 +34,9 @@ function CategoryBar(props) {
<Button
key={item.id}
className="btn-lg"
color={`${ThemeConfig[GlobalTheme].categoryNavigator}`}
color={`${ThemeConfig[GlobalTheme].buttonColor}`}
outline
active={props.currentPage === item.id}
>
<Link className="p-3" to={`/categories/${item.id}`}>
{item.name}

2
frontend/src/components/viewable/shared/footer.jsx Normal file → Executable file
View File

@ -16,7 +16,7 @@ const Footer = (props) => {
if (UserData)
return (
<footer className={`footer p-4 text-white ${ThemeConfig ? ThemeConfig[GlobalTheme].footer['background'] : ""}`} id="site-footer">
<footer className={`footer p-4 ${ThemeConfig ? ThemeConfig[GlobalTheme].footer['background'] + ' ' + ThemeConfig[GlobalTheme].footer['text'] : ""}`} id="site-footer">
<Container className='p-1'>
<Row>
<Col md="12">

0
frontend/src/components/viewable/shared/navbar.jsx Normal file → Executable file
View File

View File

View File

@ -1,5 +0,0 @@
{
"ssl": true,
"baseUrl": "",
"localBackendMode": true
}

0
frontend/src/index.css Normal file → Executable file
View File

1
frontend/src/main.jsx Normal file → Executable file
View File

@ -6,7 +6,6 @@ const ViewComponent = lazy(() =>
? import('./AppEditable.jsx')
: import('./App.jsx')
);
console.log(import.meta.env.VITE_APP_VIEW_TYPE)
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>

5
frontend/src/services/data-service.jsx Normal file → Executable file
View File

@ -1,10 +1,7 @@
import axios from 'axios'
import Config from '../config.json';
const filePostfix = Config.localBackendMode ? ".json" : ""
const getData = (endPoint) => {
return axios.get(`${Config.baseUrl}/data/${endPoint}${filePostfix}`)
return axios.get(`/data/${endPoint}.json`)
}
export default { getData }

View File

@ -0,0 +1,21 @@
import axios from 'axios';
axios.interceptors.request.use(config => {
const token = document.cookie.split(';').find(c => c.trim().startsWith('csrftoken='));
if (token) {
config.headers['X-CSRFToken'] = token.split('=')[1];
}
return config;
}, error => {
return Promise.reject(error);
});
const getData = (endPoint) => {
return axios.get(endPoint);
};
const updateData = (endPoint, data) => {
return axios.patch(endPoint, data);
};
export default { getData, updateData };

0
frontend/src/services/media-service.jsx Normal file → Executable file
View File

View File

@ -4,4 +4,13 @@ import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/data': {
target: 'http://127.0.0.1:8000',
changeOrigin: true,
secure: false,
}
}
}
})