├── .env.example
├── .github
├── CODE_OF_CONDUCT.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .idea
├── .gitignore
├── Learning-Management-System.iml
├── misc.xml
├── modules.xml
└── vcs.xml
├── Jenkinsfile
├── LICENSE
├── LMS-Backend
├── .env.example
├── .idea
│ ├── .gitignore
│ ├── LMS-Backend.iml
│ ├── inspectionProfiles
│ │ ├── Project_Default.xml
│ │ └── profiles_settings.xml
│ ├── misc.xml
│ ├── modules.xml
│ └── vcs.xml
├── Dockerfile
├── LICENSE
├── LMSBackend
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-312.pyc
│ │ ├── settings.cpython-312.pyc
│ │ ├── urls.cpython-312.pyc
│ │ └── wsgi.cpython-312.pyc
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── README.md
├── core
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-312.pyc
│ │ ├── admin.cpython-312.pyc
│ │ ├── apps.cpython-312.pyc
│ │ ├── models.cpython-312.pyc
│ │ ├── serializers.cpython-312.pyc
│ │ ├── urls.cpython-312.pyc
│ │ └── views.cpython-312.pyc
│ ├── admin.py
│ ├── apps.py
│ ├── management
│ │ ├── __init__.py
│ │ ├── __pycache__
│ │ │ └── __init__.cpython-312.pyc
│ │ └── commands
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ ├── __init__.cpython-312.pyc
│ │ │ └── seed_sample_data.cpython-312.pyc
│ │ │ └── seed_sample_data.py
│ ├── migrations
│ │ ├── __init__.py
│ │ └── __pycache__
│ │ │ └── __init__.cpython-312.pyc
│ ├── models.py
│ ├── serializers.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── db.sqlite3
├── manage.py
└── requirements.txt
├── LMS-Frontend
├── Dockerfile
├── LICENSE
├── README.md
└── app
│ ├── .editorconfig
│ ├── .gitignore
│ ├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── tasks.json
│ ├── README.md
│ ├── angular.json
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── app
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.ts
│ │ ├── app.config.server.ts
│ │ ├── app.config.ts
│ │ ├── app.routes.ts
│ │ ├── auth
│ │ │ ├── login
│ │ │ │ ├── login.component.css
│ │ │ │ ├── login.component.html
│ │ │ │ └── login.component.ts
│ │ │ └── register
│ │ │ │ ├── register.component.css
│ │ │ │ ├── register.component.html
│ │ │ │ └── register.component.ts
│ │ ├── components
│ │ │ ├── course-list
│ │ │ │ ├── course-list.component.css
│ │ │ │ ├── course-list.component.html
│ │ │ │ └── course-list.component.ts
│ │ │ ├── enrollment-list
│ │ │ │ ├── enrollment-list.component.css
│ │ │ │ ├── enrollment-list.component.html
│ │ │ │ └── enrollment-list.component.ts
│ │ │ ├── lesson-list
│ │ │ │ ├── lesson-list.component.css
│ │ │ │ ├── lesson-list.component.html
│ │ │ │ └── lesson-list.component.ts
│ │ │ ├── progress-list
│ │ │ │ ├── progress-list.component.css
│ │ │ │ ├── progress-list.component.html
│ │ │ │ └── progress-list.component.ts
│ │ │ └── user-list
│ │ │ │ ├── user-list.component.css
│ │ │ │ ├── user-list.component.html
│ │ │ │ └── user-list.component.ts
│ │ ├── core
│ │ │ ├── footer
│ │ │ │ ├── footer.component.css
│ │ │ │ ├── footer.component.html
│ │ │ │ └── footer.component.ts
│ │ │ └── header
│ │ │ │ ├── header.component.css
│ │ │ │ ├── header.component.html
│ │ │ │ └── header.component.ts
│ │ ├── pages
│ │ │ ├── home
│ │ │ │ ├── home.component.css
│ │ │ │ ├── home.component.html
│ │ │ │ └── home.component.ts
│ │ │ └── notfound
│ │ │ │ ├── notfound.component.css
│ │ │ │ ├── notfound.component.html
│ │ │ │ └── notfound.component.ts
│ │ └── services
│ │ │ ├── auth.interceptor.ts
│ │ │ ├── auth.service.ts
│ │ │ ├── course.service.ts
│ │ │ ├── enrollment.service.ts
│ │ │ ├── lesson.service.ts
│ │ │ ├── progress.service.ts
│ │ │ └── user.service.ts
│ ├── apple-touch-icon.png
│ ├── assets
│ │ ├── .gitkeep
│ │ ├── icon-192x192.png
│ │ └── icon-512x512.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ ├── manifest.json
│ └── styles.css
│ ├── tsconfig.app.json
│ ├── tsconfig.json
│ └── tsconfig.spec.json
├── README.md
├── docker-compose.yml
├── docs
├── admin-ui.png
├── browsable-api.png
├── course-ui.png
├── enrollments-ui.png
├── footer-ui.png
├── gui-tools.png
├── home-ui.png
├── lesson-ui.png
├── login-ui.png
├── mobile-ui.png
├── notfound.png
├── progress-ui.png
├── redoc-ui.png
├── register-ui.png
├── swagger-ui.png
├── unauthorized-ui.png
└── user-ui.png
├── identifier.sqlite
├── kubernetes
├── backend-deployment.yaml
├── backend-service.yaml
├── configmap.yaml
├── frontend-deployment.yaml
└── frontend-service.yaml
├── nginx
├── Dockerfile
├── docker-compose.yml
├── nginx.conf
└── start_nginx.sh
├── openapi.yaml
├── package-lock.json
└── package.json
/.env.example:
--------------------------------------------------------------------------------
1 | DJANGO_SECRET_KEY
2 | DJANGO_DEBUG
3 | DJANGO_ALLOWED_HOSTS
4 | MONGO_DB_URI
5 | MONGO_DB_USERNAME
6 | MONGO_DB_PASSWORD
7 | REDIS_LOCATION
8 |
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | ## Introduction
4 |
5 | Welcome to this Open-Source Project! This Code of Conduct outlines our expectations for participants within the community, as well as steps to report unacceptable behavior. We are committed to providing a welcoming and inspiring community for all and expect our Code of Conduct to be honored. Anyone who violates this code of conduct may be banned from the community.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | - **Using welcoming and inclusive language**
12 | - **Being respectful of differing viewpoints and experiences**
13 | - **Gracefully accepting constructive criticism**
14 | - **Focusing on what is best for the community**
15 | - **Showing empathy towards other community members**
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | - **The use of sexualized language or imagery and unwelcome sexual attention or advances**
20 | - **Trolling, insulting/derogatory comments, and personal or political attacks**
21 | - **Public or private harassment**
22 | - **Publishing others' private information, such as a physical or electronic address, without explicit permission**
23 | - **Other conduct which could reasonably be considered inappropriate in a professional setting**
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned with this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [insert email address]. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version].
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 | ---
48 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Pull Request Template
2 |
3 | ## Overview
4 | Briefly describe the purpose of this pull request. What features or bug fixes are included?
5 |
6 | ## Related Issue/Ticket
7 | Link any related issue or ticket here.
8 |
9 | ## Files Changed
10 | List the files that have been added, deleted, or modified.
11 |
12 | ## Testing
13 | Describe how you tested these changes.
14 |
15 | ## Screenshots (if applicable)
16 | Include screenshots or GIFs if you've made UI changes.
17 |
18 | ## Notes to Reviewers
19 | Add any notes for reviewers, such as areas to focus on, decisions you made, etc.
20 |
21 | ## Deployment Notes
22 | Include any necessary steps for deployment, configuration changes, etc.
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /LMS-Backend/.env
2 | /venv/
3 | /node_modules/
4 | /.env
5 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/Learning-Management-System.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | pipeline {
2 | agent any
3 | stages {
4 | stage('Install Dependencies') {
5 | steps {
6 | sh 'npm install'
7 | }
8 | }
9 | stage('Build') {
10 | steps {
11 | sh 'npm run build'
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Son Nguyen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LMS-Backend/.env.example:
--------------------------------------------------------------------------------
1 | DJANGO_SECRET_KEY
2 | DJANGO_DEBUG
3 | DJANGO_ALLOWED_HOSTS
4 | MONGO_DB_URI
5 | MONGO_DB_USERNAME
6 | MONGO_DB_PASSWORD
7 | REDIS_LOCATION
8 |
--------------------------------------------------------------------------------
/LMS-Backend/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/LMS-Backend/.idea/LMS-Backend.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/LMS-Backend/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/LMS-Backend/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LMS-Backend/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/LMS-Backend/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LMS-Backend/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LMS-Backend/Dockerfile:
--------------------------------------------------------------------------------
1 | # Use the official Python image as a parent image
2 | FROM python:3.9-slim
3 |
4 | # Set environment variables
5 | ENV PYTHONDONTWRITEBYTECODE 1
6 | ENV PYTHONUNBUFFERED 1
7 |
8 | # Set the working directory
9 | WORKDIR /app
10 |
11 | # Install system dependencies
12 | RUN apt-get update && apt-get install -y \
13 | build-essential \
14 | libpq-dev \
15 | && rm -rf /var/lib/apt/lists/*
16 |
17 | # Install Python dependencies
18 | COPY requirements.txt .
19 | RUN pip install --no-cache-dir -r requirements.txt
20 |
21 | # Copy the project files
22 | COPY . .
23 |
24 | # Expose the port
25 | EXPOSE 8000
26 |
27 | # Run the Django development server
28 | CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
29 |
--------------------------------------------------------------------------------
/LMS-Backend/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Son Nguyen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LMS-Backend/LMSBackend/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/LMSBackend/__init__.py
--------------------------------------------------------------------------------
/LMS-Backend/LMSBackend/__pycache__/__init__.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/LMSBackend/__pycache__/__init__.cpython-312.pyc
--------------------------------------------------------------------------------
/LMS-Backend/LMSBackend/__pycache__/settings.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/LMSBackend/__pycache__/settings.cpython-312.pyc
--------------------------------------------------------------------------------
/LMS-Backend/LMSBackend/__pycache__/urls.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/LMSBackend/__pycache__/urls.cpython-312.pyc
--------------------------------------------------------------------------------
/LMS-Backend/LMSBackend/__pycache__/wsgi.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/LMSBackend/__pycache__/wsgi.cpython-312.pyc
--------------------------------------------------------------------------------
/LMS-Backend/LMSBackend/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for LMSBackend 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/5.0/howto/deployment/asgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.asgi import get_asgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'LMSBackend.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/LMS-Backend/LMSBackend/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for LMSBackend project.
3 |
4 | Generated by 'django-admin startproject' using Django 5.0.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/5.0/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/5.0/ref/settings/
11 | """
12 | from datetime import timedelta
13 | from pathlib import Path
14 | from decouple import config
15 | from mongoengine import connect
16 |
17 |
18 | # Connect to MongoDB
19 | connect(
20 | db="lms_database",
21 | host=config('MONGO_DB_URI'),
22 | username=config('MONGO_DB_USERNAME'),
23 | password=config('MONGO_DB_PASSWORD'),
24 | authentication_source='admin',
25 | ssl=True
26 | )
27 |
28 |
29 | # SECURITY WARNING: keep the secret key used in production secret!
30 | SECRET_KEY = config('DJANGO_SECRET_KEY')
31 |
32 | # SECURITY WARNING: don't run with debug turned on in production!
33 | DEBUG = config('DJANGO_DEBUG', default=False, cast=bool)
34 |
35 |
36 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
37 | BASE_DIR = Path(__file__).resolve().parent.parent
38 |
39 |
40 | # Quick-start development settings - unsuitable for production
41 | # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
42 |
43 | ALLOWED_HOSTS = ['*']
44 |
45 |
46 | # Application definition
47 |
48 | INSTALLED_APPS = [
49 | 'django.contrib.admin',
50 | 'django.contrib.auth',
51 | 'django.contrib.contenttypes',
52 | 'django.contrib.sessions',
53 | 'django.contrib.messages',
54 | 'django.contrib.staticfiles',
55 | 'rest_framework',
56 | 'rest_framework.authtoken',
57 | 'rest_framework_mongoengine',
58 | 'core',
59 | 'corsheaders',
60 | 'drf_yasg',
61 | 'dj_rest_auth',
62 | 'dj_rest_auth.registration',
63 | 'allauth',
64 | 'allauth.account',
65 | 'allauth.socialaccount',
66 | 'django.contrib.sites',
67 | ]
68 |
69 | SITE_ID = 1
70 |
71 | DATABASES = {
72 | 'default': {
73 | 'ENGINE': 'django.db.backends.sqlite3',
74 | 'NAME': BASE_DIR / 'db.sqlite3',
75 | }
76 | }
77 |
78 | MIDDLEWARE = [
79 | 'django.middleware.security.SecurityMiddleware',
80 | 'django.contrib.sessions.middleware.SessionMiddleware',
81 | 'django.middleware.common.CommonMiddleware',
82 | 'django.middleware.csrf.CsrfViewMiddleware',
83 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
84 | 'django.contrib.messages.middleware.MessageMiddleware',
85 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
86 | 'allauth.account.middleware.AccountMiddleware',
87 | 'corsheaders.middleware.CorsMiddleware',
88 | ]
89 |
90 | ROOT_URLCONF = 'LMSBackend.urls'
91 |
92 | # CORS settings
93 | CORS_ALLOW_ALL_ORIGINS = True # Allow all origins
94 |
95 | # Explicitly allow specific origins
96 | # CORS_ALLOWED_ORIGINS = [
97 | # 'https://learning-management-system-fullstack.vercel.app', # Frontend hosted on Vercel
98 | # 'https://fullstack-learning-management-system.netlify.app', # Backup Frontend hosted on Netlify
99 | # 'http://localhost:4200', # Local development
100 | # ]
101 |
102 | CORS_ALLOW_CREDENTIALS = True # Allow sending credentials like cookies and auth headers
103 |
104 | TEMPLATES = [
105 | {
106 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
107 | 'DIRS': [],
108 | 'APP_DIRS': True,
109 | 'OPTIONS': {
110 | 'context_processors': [
111 | 'django.template.context_processors.debug',
112 | 'django.template.context_processors.request',
113 | 'django.contrib.auth.context_processors.auth',
114 | 'django.contrib.messages.context_processors.messages',
115 | ],
116 | },
117 | },
118 | ]
119 |
120 | WSGI_APPLICATION = 'LMSBackend.wsgi.application'
121 |
122 | CACHES = {
123 | 'default': {
124 | 'BACKEND': 'django_redis.cache.RedisCache',
125 | 'LOCATION': config('REDIS_LOCATION'),
126 | 'OPTIONS': {
127 | 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
128 | }
129 | }
130 | }
131 |
132 | SESSION_ENGINE = "django.contrib.sessions.backends.cache"
133 | SESSION_CACHE_ALIAS = "default"
134 |
135 | REST_FRAMEWORK = {
136 | 'DEFAULT_AUTHENTICATION_CLASSES': [
137 | 'rest_framework_simplejwt.authentication.JWTAuthentication', # JWT Authentication
138 | 'rest_framework.authentication.TokenAuthentication',
139 | ],
140 | 'DEFAULT_PERMISSION_CLASSES': [
141 | 'rest_framework.permissions.IsAuthenticated',
142 | ],
143 | }
144 |
145 | SWAGGER_SETTINGS = {
146 | 'USE_SESSION_AUTH': False,
147 | 'SECURITY_DEFINITIONS': {
148 | 'Bearer': {
149 | 'type': 'apiKey',
150 | 'in': 'header',
151 | 'name': 'Authorization',
152 | 'description': 'Enter your token in the format: Token {your_token}'
153 | }
154 | },
155 | 'DEFAULT_API_KEY': 'Token ',
156 | 'APIS_SORTER': 'alpha',
157 | 'OPERATIONS_SORTER': 'alpha',
158 | 'SHOW_REQUEST_HEADERS': True,
159 | }
160 |
161 | SIMPLE_JWT = {
162 | 'ACCESS_TOKEN_LIFETIME': timedelta(days=7),
163 | 'REFRESH_TOKEN_LIFETIME': timedelta(days=14),
164 | }
165 |
166 | ACCOUNT_EMAIL_VERIFICATION = 'none'
167 | ACCOUNT_AUTHENTICATION_METHOD = 'username'
168 | ACCOUNT_EMAIL_REQUIRED = False
169 |
170 |
171 | # Password validation
172 | # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
173 |
174 | AUTH_PASSWORD_VALIDATORS = [
175 | {
176 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
177 | },
178 | {
179 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
180 | },
181 | {
182 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
183 | },
184 | {
185 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
186 | },
187 | ]
188 |
189 |
190 | # Internationalization
191 | # https://docs.djangoproject.com/en/5.0/topics/i18n/
192 |
193 | LANGUAGE_CODE = 'en-us'
194 |
195 | TIME_ZONE = 'UTC'
196 |
197 | USE_I18N = True
198 |
199 | USE_TZ = True
200 |
201 |
202 | # Static files (CSS, JavaScript, Images)
203 | # https://docs.djangoproject.com/en/5.0/howto/static-files/
204 |
205 | STATIC_URL = 'static/'
206 |
207 |
208 | # Default primary key field type
209 | # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
210 |
211 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
212 |
--------------------------------------------------------------------------------
/LMS-Backend/LMSBackend/urls.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.urls import path, include
3 | from django.views.generic import RedirectView
4 | from rest_framework import permissions
5 | from drf_yasg.views import get_schema_view
6 | from drf_yasg import openapi
7 |
8 | schema_view = get_schema_view(
9 | openapi.Info(
10 | title="Learning Management System API",
11 | default_version='v1',
12 | description="Comprehensive API documentation for the Learning Management System",
13 | terms_of_service="https://learning-management-system-fullstack.vercel.app/",
14 | contact=openapi.Contact(email="hoangson091104@gmail.com", name="Learning Management System", url="https://learning-management-system-fullstack.vercel.app/"),
15 | license=openapi.License(name="MIT License"),
16 | ),
17 | public=True,
18 | permission_classes=[permissions.AllowAny],
19 | )
20 |
21 | urlpatterns = [
22 | path('admin/', admin.site.urls),
23 | path('api/', include('core.urls')),
24 | path('api/auth/', include('dj_rest_auth.urls')),
25 | path('api/auth/registration/', include('dj_rest_auth.registration.urls')),
26 | path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
27 | path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
28 | path('', RedirectView.as_view(url='/swagger', permanent=False)), # Redirect base URL to Swagger UI URL
29 | ]
30 |
--------------------------------------------------------------------------------
/LMS-Backend/LMSBackend/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for LMSBackend 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/5.0/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'LMSBackend.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/LMS-Backend/core/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/core/__init__.py
--------------------------------------------------------------------------------
/LMS-Backend/core/__pycache__/__init__.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/core/__pycache__/__init__.cpython-312.pyc
--------------------------------------------------------------------------------
/LMS-Backend/core/__pycache__/admin.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/core/__pycache__/admin.cpython-312.pyc
--------------------------------------------------------------------------------
/LMS-Backend/core/__pycache__/apps.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/core/__pycache__/apps.cpython-312.pyc
--------------------------------------------------------------------------------
/LMS-Backend/core/__pycache__/models.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/core/__pycache__/models.cpython-312.pyc
--------------------------------------------------------------------------------
/LMS-Backend/core/__pycache__/serializers.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/core/__pycache__/serializers.cpython-312.pyc
--------------------------------------------------------------------------------
/LMS-Backend/core/__pycache__/urls.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/core/__pycache__/urls.cpython-312.pyc
--------------------------------------------------------------------------------
/LMS-Backend/core/__pycache__/views.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/core/__pycache__/views.cpython-312.pyc
--------------------------------------------------------------------------------
/LMS-Backend/core/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.utils.translation import gettext_lazy as _
3 |
4 | admin.site.site_header = _("Learning Management System Administration")
5 | admin.site.site_title = _("Learning Management System Admin")
6 | admin.site.index_title = _("Welcome to the Learning Management System Admin")
7 |
--------------------------------------------------------------------------------
/LMS-Backend/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 |
--------------------------------------------------------------------------------
/LMS-Backend/core/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/core/management/__init__.py
--------------------------------------------------------------------------------
/LMS-Backend/core/management/__pycache__/__init__.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/core/management/__pycache__/__init__.cpython-312.pyc
--------------------------------------------------------------------------------
/LMS-Backend/core/management/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/core/management/commands/__init__.py
--------------------------------------------------------------------------------
/LMS-Backend/core/management/commands/__pycache__/__init__.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/core/management/commands/__pycache__/__init__.cpython-312.pyc
--------------------------------------------------------------------------------
/LMS-Backend/core/management/commands/__pycache__/seed_sample_data.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/core/management/commands/__pycache__/seed_sample_data.cpython-312.pyc
--------------------------------------------------------------------------------
/LMS-Backend/core/management/commands/seed_sample_data.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import BaseCommand
2 | from core.models import User, Course, Category, Lesson, Quiz, Question, Choice, Enrollment, Progress, Notification
3 | from datetime import datetime
4 | from faker import Faker
5 | import random
6 |
7 |
8 | class Command(BaseCommand):
9 | help = 'Seed realistic sample data for all API endpoints'
10 |
11 | def handle(self, *args, **kwargs):
12 | # Initialize Faker
13 | fake = Faker()
14 |
15 | # Clear existing data
16 | User.drop_collection()
17 | Category.drop_collection()
18 | Course.drop_collection()
19 | Lesson.drop_collection()
20 | Quiz.drop_collection()
21 | Question.drop_collection()
22 | Choice.drop_collection()
23 | Enrollment.drop_collection()
24 | Progress.drop_collection()
25 | Notification.drop_collection()
26 |
27 | # Seed Users
28 | users = [
29 | User(
30 | username=fake.user_name(),
31 | email=fake.email(),
32 | is_instructor=random.choice([True, False]),
33 | is_student=random.choice([True, False]),
34 | bio=fake.paragraph(nb_sentences=2),
35 | profile_picture=fake.image_url()
36 | ).save()
37 | for _ in range(10)
38 | ]
39 |
40 | # Seed Categories
41 | categories = [
42 | Category(
43 | name=fake.word().capitalize(),
44 | description=fake.sentence(nb_words=6)
45 | ).save()
46 | for _ in range(5)
47 | ]
48 |
49 | # Seed Courses
50 | courses = [
51 | Course(
52 | title=fake.catch_phrase(),
53 | description=fake.paragraph(nb_sentences=5),
54 | instructor=random.choice(users),
55 | category=random.choice(categories),
56 | created_at=datetime.now(),
57 | updated_at=datetime.now(),
58 | image=fake.image_url(),
59 | price=random.uniform(50, 500),
60 | published=random.choice([True, False])
61 | ).save()
62 | for _ in range(8)
63 | ]
64 |
65 | # Seed Lessons
66 | lessons = [
67 | Lesson(
68 | title=fake.sentence(nb_words=4),
69 | course=random.choice(courses),
70 | content=fake.text(max_nb_chars=200),
71 | video_url=fake.url(),
72 | created_at=datetime.now(),
73 | updated_at=datetime.now()
74 | ).save()
75 | for _ in range(15)
76 | ]
77 |
78 | # Seed Quizzes
79 | quizzes = [
80 | Quiz(
81 | lesson=random.choice(lessons),
82 | title=fake.sentence(nb_words=3),
83 | created_at=datetime.now()
84 | ).save()
85 | for _ in range(10)
86 | ]
87 |
88 | # Seed Questions
89 | questions = [
90 | Question(
91 | quiz=random.choice(quizzes),
92 | text=fake.sentence(nb_words=10) + "?",
93 | answer=fake.word(),
94 | created_at=datetime.now()
95 | ).save()
96 | for _ in range(20)
97 | ]
98 |
99 | # Seed Choices
100 | choices = [
101 | Choice(
102 | question=random.choice(questions),
103 | text=fake.sentence(nb_words=4),
104 | is_correct=random.choice([True, False])
105 | ).save()
106 | for _ in range(40)
107 | ]
108 |
109 | # Seed Enrollments
110 | enrollments = [
111 | Enrollment(
112 | student=random.choice(users),
113 | course=random.choice(courses),
114 | enrolled_at=datetime.now()
115 | ).save()
116 | for _ in range(10)
117 | ]
118 |
119 | # Seed Progress
120 | progress_records = [
121 | Progress(
122 | student=random.choice(users),
123 | lesson=random.choice(lessons),
124 | completed=random.choice([True, False]),
125 | completed_at=datetime.now() if random.choice([True, False]) else None
126 | ).save()
127 | for _ in range(10)
128 | ]
129 |
130 | # Seed Notifications
131 | notifications = [
132 | Notification(
133 | recipient=random.choice(users),
134 | message=fake.sentence(nb_words=6),
135 | created_at=datetime.now(),
136 | is_read=random.choice([True, False])
137 | ).save()
138 | for _ in range(10)
139 | ]
140 |
141 | self.stdout.write(self.style.SUCCESS('Successfully seeded realistic sample data'))
142 |
--------------------------------------------------------------------------------
/LMS-Backend/core/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/core/migrations/__init__.py
--------------------------------------------------------------------------------
/LMS-Backend/core/migrations/__pycache__/__init__.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/core/migrations/__pycache__/__init__.cpython-312.pyc
--------------------------------------------------------------------------------
/LMS-Backend/core/models.py:
--------------------------------------------------------------------------------
1 | from mongoengine import Document, fields
2 |
3 |
4 | class User(Document):
5 | username = fields.StringField(required=True, max_length=100, unique=True)
6 | email = fields.EmailField(required=True, unique=True)
7 | is_instructor = fields.BooleanField(default=False)
8 | is_student = fields.BooleanField(default=True)
9 | bio = fields.StringField()
10 | profile_picture = fields.StringField()
11 |
12 | meta = {'collection': 'users'}
13 |
14 |
15 | class Category(Document):
16 | name = fields.StringField(max_length=100)
17 | description = fields.StringField()
18 |
19 | meta = {'collection': 'categories'}
20 |
21 |
22 | class Course(Document):
23 | title = fields.StringField(max_length=255)
24 | description = fields.StringField()
25 | instructor = fields.ReferenceField(User, reverse_delete_rule='CASCADE')
26 | category = fields.ReferenceField(Category, reverse_delete_rule='NULLIFY')
27 | created_at = fields.DateTimeField()
28 | updated_at = fields.DateTimeField()
29 | image = fields.StringField()
30 | price = fields.DecimalField(min_value=0)
31 | published = fields.BooleanField(default=False)
32 |
33 | meta = {'collection': 'courses'}
34 |
35 |
36 | class Lesson(Document):
37 | title = fields.StringField(max_length=255)
38 | course = fields.ReferenceField(Course, reverse_delete_rule='CASCADE')
39 | content = fields.StringField()
40 | video_url = fields.StringField()
41 | created_at = fields.DateTimeField()
42 | updated_at = fields.DateTimeField()
43 |
44 | meta = {'collection': 'lessons'}
45 |
46 |
47 | class Quiz(Document):
48 | lesson = fields.ReferenceField(Lesson, reverse_delete_rule='CASCADE')
49 | title = fields.StringField(max_length=255)
50 | created_at = fields.DateTimeField()
51 |
52 | meta = {'collection': 'quizzes'}
53 |
54 |
55 | class Question(Document):
56 | quiz = fields.ReferenceField(Quiz, reverse_delete_rule='CASCADE')
57 | text = fields.StringField(max_length=1024)
58 | answer = fields.StringField(max_length=1024)
59 | created_at = fields.DateTimeField()
60 |
61 | meta = {'collection': 'questions'}
62 |
63 |
64 | class Choice(Document):
65 | question = fields.ReferenceField(Question, reverse_delete_rule='CASCADE')
66 | text = fields.StringField(max_length=255)
67 | is_correct = fields.BooleanField(default=False)
68 |
69 | meta = {'collection': 'choices'}
70 |
71 |
72 | class Enrollment(Document):
73 | student = fields.ReferenceField(User, reverse_delete_rule='CASCADE')
74 | course = fields.ReferenceField(Course, reverse_delete_rule='CASCADE')
75 | enrolled_at = fields.DateTimeField()
76 |
77 | meta = {'collection': 'enrollments'}
78 |
79 |
80 | class Progress(Document):
81 | student = fields.ReferenceField(User, reverse_delete_rule='CASCADE')
82 | lesson = fields.ReferenceField(Lesson, reverse_delete_rule='CASCADE')
83 | completed = fields.BooleanField(default=False)
84 | completed_at = fields.DateTimeField()
85 |
86 | meta = {'collection': 'progress'}
87 |
88 |
89 | class Notification(Document):
90 | recipient = fields.ReferenceField(User, reverse_delete_rule='CASCADE')
91 | message = fields.StringField(max_length=255)
92 | created_at = fields.DateTimeField()
93 | is_read = fields.BooleanField(default=False)
94 |
95 | meta = {'collection': 'notifications'}
96 |
--------------------------------------------------------------------------------
/LMS-Backend/core/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework_mongoengine.serializers import DocumentSerializer
2 | from .models import User, Category, Course, Lesson, Quiz, Question, Choice, Enrollment, Progress, Notification
3 |
4 |
5 | class UserSerializer(DocumentSerializer):
6 | class Meta:
7 | model = User
8 | fields = '__all__'
9 |
10 |
11 | class CategorySerializer(DocumentSerializer):
12 | class Meta:
13 | model = Category
14 | fields = '__all__'
15 |
16 |
17 | class CourseSerializer(DocumentSerializer):
18 | class Meta:
19 | model = Course
20 | fields = '__all__'
21 |
22 |
23 | class LessonSerializer(DocumentSerializer):
24 | class Meta:
25 | model = Lesson
26 | fields = '__all__'
27 |
28 |
29 | class QuizSerializer(DocumentSerializer):
30 | class Meta:
31 | model = Quiz
32 | fields = '__all__'
33 |
34 |
35 | class QuestionSerializer(DocumentSerializer):
36 | class Meta:
37 | model = Question
38 | fields = '__all__'
39 |
40 |
41 | class ChoiceSerializer(DocumentSerializer):
42 | class Meta:
43 | model = Choice
44 | fields = '__all__'
45 |
46 |
47 | class EnrollmentSerializer(DocumentSerializer):
48 | class Meta:
49 | model = Enrollment
50 | fields = '__all__'
51 |
52 |
53 | class ProgressSerializer(DocumentSerializer):
54 | class Meta:
55 | model = Progress
56 | fields = '__all__'
57 |
58 |
59 | class NotificationSerializer(DocumentSerializer):
60 | class Meta:
61 | model = Notification
62 | fields = '__all__'
63 |
--------------------------------------------------------------------------------
/LMS-Backend/core/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 | from rest_framework.test import APIClient
3 | from rest_framework import status
4 | from mongoengine import connect, disconnect
5 | from .models import User, Category, Course, Lesson, Quiz, Question, Choice, Enrollment, Progress, Notification
6 |
7 |
8 | class BaseAPITestCase(TestCase):
9 | """
10 | Base test case to handle setup and teardown for MongoDB.
11 | """
12 |
13 | def setUp(self):
14 | # Connect to test database
15 | connect('mongoenginetest', host='mongomock://localhost')
16 |
17 | self.client = APIClient()
18 |
19 | def tearDown(self):
20 | disconnect()
21 |
22 |
23 | class UserModelTest(BaseAPITestCase):
24 | def test_create_user(self):
25 | user = User(username='testuser', email='test@example.com', is_instructor=True, bio='An experienced instructor')
26 | user.save()
27 |
28 | # Check if user is saved correctly
29 | self.assertEqual(User.objects.count(), 1)
30 | self.assertEqual(user.username, 'testuser')
31 | self.assertTrue(user.is_instructor)
32 |
33 |
34 | class CourseModelTest(BaseAPITestCase):
35 | def test_create_course(self):
36 | instructor = User(username='instructor', email='instructor@example.com', is_instructor=True)
37 | instructor.save()
38 |
39 | category = Category(name='Programming', description='Learn programming languages')
40 | category.save()
41 |
42 | course = Course(title='Python Basics', description='Learn the basics of Python', instructor=instructor,
43 | category=category)
44 | course.save()
45 |
46 | self.assertEqual(Course.objects.count(), 1)
47 | self.assertEqual(course.title, 'Python Basics')
48 | self.assertEqual(course.instructor.username, 'instructor')
49 |
50 |
51 | class UserViewSetTest(BaseAPITestCase):
52 | def test_user_list(self):
53 | User(username='user1', email='user1@example.com').save()
54 | User(username='user2', email='user2@example.com').save()
55 |
56 | response = self.client.get('/users/')
57 |
58 | self.assertEqual(response.status_code, status.HTTP_200_OK)
59 | self.assertEqual(len(response.data), 2)
60 |
61 | def test_user_create(self):
62 | user_data = {
63 | "username": "testuser",
64 | "email": "testuser@example.com",
65 | "is_instructor": True,
66 | "bio": "An experienced instructor"
67 | }
68 | response = self.client.post('/users/', user_data, format='json')
69 |
70 | self.assertEqual(response.status_code, status.HTTP_201_CREATED)
71 | self.assertEqual(User.objects.count(), 1)
72 | self.assertEqual(User.objects.first().username, 'testuser')
73 |
74 |
75 | class CourseViewSetTest(BaseAPITestCase):
76 | def test_course_list(self):
77 | instructor = User(username='instructor', email='instructor@example.com', is_instructor=True)
78 | instructor.save()
79 |
80 | category = Category(name='Programming', description='Learn programming languages')
81 | category.save()
82 |
83 | Course(title='Python Basics', description='Learn Python', instructor=instructor, category=category).save()
84 | Course(title='Django Basics', description='Learn Django', instructor=instructor, category=category).save()
85 |
86 | response = self.client.get('/courses/')
87 |
88 | self.assertEqual(response.status_code, status.HTTP_200_OK)
89 | self.assertEqual(len(response.data), 2)
90 |
91 | def test_course_create(self):
92 | instructor = User(username='instructor', email='instructor@example.com', is_instructor=True)
93 | instructor.save()
94 |
95 | category = Category(name='Programming', description='Learn programming languages')
96 | category.save()
97 |
98 | course_data = {
99 | "title": "JavaScript Basics",
100 | "description": "Learn JavaScript",
101 | "instructor": str(instructor.id),
102 | "category": str(category.id),
103 | "price": 19.99
104 | }
105 | response = self.client.post('/courses/', course_data, format='json')
106 |
107 | self.assertEqual(response.status_code, status.HTTP_201_CREATED)
108 | self.assertEqual(Course.objects.count(), 1)
109 | self.assertEqual(Course.objects.first().title, 'JavaScript Basics')
110 |
111 |
112 | class LessonViewSetTest(BaseAPITestCase):
113 | def test_lesson_create(self):
114 | instructor = User(username='instructor', email='instructor@example.com', is_instructor=True)
115 | instructor.save()
116 |
117 | category = Category(name='Programming', description='Learn programming languages')
118 | category.save()
119 |
120 | course = Course(title='Python Basics', description='Learn Python', instructor=instructor, category=category)
121 | course.save()
122 |
123 | lesson_data = {
124 | "title": "Introduction to Python",
125 | "course": str(course.id),
126 | "content": "This is an introduction to Python programming."
127 | }
128 | response = self.client.post('/lessons/', lesson_data, format='json')
129 |
130 | self.assertEqual(response.status_code, status.HTTP_201_CREATED)
131 | self.assertEqual(Lesson.objects.count(), 1)
132 | self.assertEqual(Lesson.objects.first().title, 'Introduction to Python')
133 |
134 |
135 | class QuizViewSetTest(BaseAPITestCase):
136 | def test_quiz_create(self):
137 | instructor = User(username='instructor', email='instructor@example.com', is_instructor=True)
138 | instructor.save()
139 |
140 | category = Category(name='Programming', description='Learn programming languages')
141 | category.save()
142 |
143 | course = Course(title='Python Basics', description='Learn Python', instructor=instructor, category=category)
144 | course.save()
145 |
146 | lesson = Lesson(title='Introduction to Python', course=course, content='Introduction content')
147 | lesson.save()
148 |
149 | quiz_data = {
150 | "title": "Python Basics Quiz",
151 | "lesson": str(lesson.id)
152 | }
153 | response = self.client.post('/quizzes/', quiz_data, format='json')
154 |
155 | self.assertEqual(response.status_code, status.HTTP_201_CREATED)
156 | self.assertEqual(Quiz.objects.count(), 1)
157 | self.assertEqual(Quiz.objects.first().title, 'Python Basics Quiz')
158 |
--------------------------------------------------------------------------------
/LMS-Backend/core/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path, include
2 | from rest_framework.routers import DefaultRouter
3 | from .views import (UserViewSet, CourseViewSet, CategoryViewSet, LessonViewSet, QuizViewSet,
4 | QuestionViewSet, ChoiceViewSet, EnrollmentViewSet, ProgressViewSet, NotificationViewSet)
5 |
6 | router = DefaultRouter()
7 | router.register(r'users', UserViewSet, basename='user')
8 | router.register(r'courses', CourseViewSet, basename='course')
9 | router.register(r'categories', CategoryViewSet, basename='category')
10 | router.register(r'lessons', LessonViewSet, basename='lesson')
11 | router.register(r'quizzes', QuizViewSet, basename='quiz')
12 | router.register(r'questions', QuestionViewSet, basename='question')
13 | router.register(r'choices', ChoiceViewSet, basename='choice')
14 | router.register(r'enrollments', EnrollmentViewSet, basename='enrollment')
15 | router.register(r'progress', ProgressViewSet, basename='progress')
16 | router.register(r'notifications', NotificationViewSet, basename='notification')
17 |
18 | urlpatterns = [
19 | path('', include(router.urls)),
20 | ]
21 |
--------------------------------------------------------------------------------
/LMS-Backend/db.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Backend/db.sqlite3
--------------------------------------------------------------------------------
/LMS-Backend/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Django's command-line utility for administrative tasks."""
3 | import os
4 | import sys
5 |
6 |
7 | def main():
8 | """Run administrative tasks."""
9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'LMSBackend.settings')
10 | try:
11 | from django.core.management import execute_from_command_line
12 | # Seed sample data
13 | execute_from_command_line(['manage.py', 'seed_sample_data'])
14 | except ImportError as exc:
15 | raise ImportError(
16 | "Couldn't import Django. Are you sure it's installed and "
17 | "available on your PYTHONPATH environment variable? Did you "
18 | "forget to activate a virtual environment?"
19 | ) from exc
20 | execute_from_command_line(sys.argv)
21 |
22 |
23 | if __name__ == '__main__':
24 | main()
25 |
--------------------------------------------------------------------------------
/LMS-Backend/requirements.txt:
--------------------------------------------------------------------------------
1 | asgiref==3.8.1
2 | certifi==2024.8.30
3 | charset-normalizer==3.3.2
4 | dataclasses==0.6
5 | dj-rest-auth==6.0.0
6 | Django==4.2.16
7 | django-allauth==64.2.1
8 | django-cors-headers==4.4.0
9 | django-mongoengine==0.5.6
10 | django-redis==5.4.0
11 | django-rest-framework-mongoengine==3.4.1
12 | djangorestframework==3.15.2
13 | djangorestframework-simplejwt==5.3.1
14 | djongo==1.2.31
15 | dnspython==2.6.1
16 | drf-yasg==1.21.8
17 | Faker==28.4.1
18 | idna==3.8
19 | inflection==0.5.1
20 | mongoengine==0.29.0
21 | packaging==24.1
22 | PyJWT==2.9.0
23 | pymongo==4.8.0
24 | python-dateutil==2.9.0.post0
25 | python-decouple==3.8
26 | pytz==2024.1
27 | PyYAML==6.0.2
28 | redis==5.0.8
29 | requests==2.32.3
30 | setuptools==74.1.2
31 | six==1.16.0
32 | sqlparse==0.5.1
33 | uritemplate==4.1.1
34 | urllib3==2.2.2
35 |
--------------------------------------------------------------------------------
/LMS-Frontend/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18.19
2 |
3 | # Set the working directory
4 | WORKDIR /app
5 |
6 | # Copy package.json and package-lock.json from the Angular app directory
7 | COPY ./app/package*.json ./
8 |
9 | # Install Angular CLI globally
10 | RUN npm install -g @angular/cli
11 |
12 | # Install frontend dependencies
13 | RUN npm install
14 |
15 | # Copy the entire Angular app to the working directory
16 | COPY ./app .
17 |
18 | # Build the Angular application for production
19 | RUN ng build --configuration production
20 |
21 | # Install a lightweight HTTP server to serve the application
22 | RUN npm install -g http-server
23 |
24 | # Expose the port the application will run on
25 | EXPOSE 8080
26 |
27 | # Serve the Angular application
28 | CMD ["http-server", "dist/your-angular-app-name", "-p", "8080"]
29 |
--------------------------------------------------------------------------------
/LMS-Frontend/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Son Nguyen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LMS-Frontend/README.md:
--------------------------------------------------------------------------------
1 | # E-Learning Management System Frontend
2 |
3 | The **E-Learning Management System Frontend** is the frontend component of the E-Learning Management System, a web-based application that allows administrators and users to interact with the e-learning platform's various components. The frontend is developed with Angular, and the backend is built using Django REST Framework.
4 |
5 | ## Table of Contents
6 |
7 | - [Project Overview](#project-overview)
8 | - [Features](#features)
9 | - [User Interfaces](#user-interfaces)
10 | - [File Structure](#file-structure)
11 | - [Getting Started](#getting-started)
12 | - [Prerequisites](#prerequisites)
13 | - [Backend Setup (Django)](#backend-setup)
14 | - [Frontend Setup (Angular)](#frontend-setup)
15 | - [Styling and Design](#styling-and-design)
16 | - [Troubleshooting](#troubleshooting)
17 | - [Contributing](#contributing)
18 | - [License](#license)
19 | - [Contact](#contact)
20 |
21 | ## Project Overview
22 |
23 | This frontend project is built with Angular, a popular web application framework developed by Google. It provides a user-friendly interface for managing courses, lessons, users, enrollments, and progress tracking. The frontend interacts with the backend APIs defined by Django to fetch and update data, handle user authentication, and display dynamic content.
24 |
25 | ## Features
26 |
27 | - **User Authentication**: Token-based login system.
28 | - **Course Management**: Display, create, update, and delete courses.
29 | - **Lesson Management**: Display, create, update, and delete lessons.
30 | - **User Management**: List and manage users.
31 | - **Enrollment Management**: Handle course enrollments.
32 | - **Progress Tracking**: Monitor user progress across lessons.
33 | - **Responsive Design**: Optimized for various devices.
34 | - **Charts and Data Visualization**: Implemented with Chart.js for dynamic data visualization.
35 |
36 | ## User Interfaces
37 |
38 | **Home Page**:
39 |
40 |
41 |
42 |
43 |
44 | **Course List**:
45 |
46 |
47 |
48 |
49 |
50 | **Lesson List**:
51 |
52 |
53 |
54 |
55 |
56 | **User List**:
57 |
58 |
59 |
60 |
61 |
62 | **Enrollment List**:
63 |
64 |
65 |
66 |
67 |
68 | **Progress List**:
69 |
70 |
71 |
72 |
73 |
74 | **Login Page**:
75 |
76 |
77 |
78 |
79 |
80 | ## File Structure
81 |
82 | The repository is organized as follows:
83 |
84 | ```plaintext
85 | Learning-Management-System/
86 | ├── LMS-Backend
87 | │ ├── (Backend code)
88 | │ └── ...
89 | ├── LMS-Frontend
90 | │ ├── angular.json
91 | │ ├── package.json
92 | │ ├── README.md
93 | │ ├── LICENSE
94 | │ ├── app/
95 | │ │ ├── src/
96 | │ │ │ ├── app/
97 | │ │ │ │ ├── auth/
98 | │ │ │ │ │ ├── login/
99 | │ │ │ │ │ │ ├── login.component.ts
100 | │ │ │ │ │ │ ├── login.component.html
101 | │ │ │ │ │ │ └── login.component.css
102 | │ │ │ │ ├── core/
103 | │ │ │ │ │ ├── footer/
104 | │ │ │ │ │ │ ├── footer.component.ts
105 | │ │ │ │ │ │ ├── footer.component.html
106 | │ │ │ │ │ │ └── footer.component.css
107 | │ │ │ │ │ ├── header/
108 | │ │ │ │ │ │ ├── header.component.ts
109 | │ │ │ │ │ │ ├── header.component.html
110 | │ │ │ │ │ │ └── header.component.css
111 | │ │ │ │ ├── pages/
112 | │ │ │ │ │ ├── home/
113 | │ │ │ │ │ │ ├── notfound.component.ts
114 | │ │ │ │ │ │ ├── notfound.component.html
115 | │ │ │ │ │ │ └── home.component.css
116 | │ │ │ │ ├── components/
117 | │ │ │ │ │ ├── course-list/
118 | │ │ │ │ │ │ ├── course-list.component.ts
119 | │ │ │ │ │ │ ├── course-list.component.html
120 | │ │ │ │ │ │ └── course-list.component.css
121 | │ │ │ │ │ ├── lesson-list/
122 | │ │ │ │ │ │ ├── lesson-list.component.ts
123 | │ │ │ │ │ │ ├── lesson-list.component.html
124 | │ │ │ │ │ │ └── lesson-list.component.css
125 | │ │ │ │ │ ├── user-list/
126 | │ │ │ │ │ │ ├── user-list.component.ts
127 | │ │ │ │ │ │ ├── user-list.component.html
128 | │ │ │ │ │ │ └── user-list.component.css
129 | │ │ │ │ │ ├── enrollment-list/
130 | │ │ │ │ │ │ ├── enrollment-list.component.ts
131 | │ │ │ │ │ │ ├── enrollment-list.component.html
132 | │ │ │ │ │ │ └── enrollment-list.component.css
133 | │ │ │ │ │ ├── progress-list/
134 | │ │ │ │ │ │ ├── progress-list.component.ts
135 | │ │ │ │ │ │ ├── progress-list.component.html
136 | │ │ │ │ │ │ └── progress-list.component.css
137 | │ │ │ │ ├── services/
138 | │ │ │ │ │ ├── auth.interceptor.ts
139 | │ │ │ │ │ ├── auth.service.ts
140 | │ │ │ │ │ ├── user.service.ts
141 | │ │ │ │ │ ├── course.service.ts
142 | │ │ │ │ │ ├── lesson.service.ts
143 | │ │ │ │ │ ├── enrollment.service.ts
144 | │ │ │ │ │ └── progress.service.ts
145 | │ │ │ │ ├── app.routes.ts
146 | │ │ │ │ ├── app.component.ts
147 | │ │ │ │ ├── app.config.ts
148 | │ │ │ │ ├── app.config.service.ts
149 | │ │ │ │ ├── app.component.html
150 | │ │ │ │ └── app.component.css
151 | │ │ │ ├── assets/
152 | │ │ │ │ └── images/
153 | │ │ │ │ └── .gitkeep
154 | │ │ │ ├── main.ts
155 | │ │ │ ├── styles.css
156 | │ │ │ └── index.html
157 | │ │ ├── .editorconfig
158 | │ │ ├── .gitignore
159 | │ │ ├── angular.json
160 | │ │ ├── package.json
161 | │ │ ├── package-lock.json
162 | │ │ ├── tsconfig.json
163 | │ │ ├── tsconfig.app.json
164 | │ │ └── tsconfig.spec.json
165 | │ ├── LICENSE
166 | │ ├── README.md
167 | ├── .gitignore
168 | ├── LICENSE
169 | └── README.md
170 | ```
171 |
172 | ## Getting Started
173 |
174 | ### Prerequisites
175 |
176 | Make sure you have the following installed on your machine:
177 |
178 | - **Node.js** (v18.19 or later)
179 | - **Angular CLI** (v12 or later)
180 | - **Python** (v3.7 or later)
181 |
182 | To check if you have these installed, run:
183 |
184 | ```bash
185 | node -v
186 | npm -v
187 | ng version
188 | python --version
189 | ```
190 |
191 | ### Backend Setup
192 |
193 | It is of utmost importance to set up the backend server before running the frontend application.
194 |
195 | 1. **Navigate to the backend directory:**
196 |
197 | ```bash
198 | cd Learning-Management-System/LMS-Backend
199 | ```
200 |
201 | 2. **Create a virtual environment:**
202 |
203 | ```bash
204 | python -m venv venv
205 | source venv/bin/activate # On Windows, use `venv\Scripts\activate`
206 | ```
207 |
208 | 3. **Install dependencies:**
209 |
210 | ```bash
211 | pip install -r requirements.txt
212 | ```
213 |
214 | 4. **Run the backend server:**
215 |
216 | ```bash
217 | python manage.py runserver
218 | ```
219 |
220 | ### Frontend Setup
221 |
222 | 1. **Navigate to the frontend directory:**
223 |
224 | ```bash
225 | cd Learning-Management-System/LMS-Frontend/app
226 | ```
227 |
228 | 2. **Install dependencies:**
229 |
230 | ```bash
231 | npm install
232 | ```
233 |
234 | 3. **Start the development server:**
235 |
236 | ```bash
237 | ng serve
238 | ```
239 |
240 | 4. **Open your browser and navigate to:**
241 |
242 | ```
243 | http://localhost:4200
244 | ```
245 |
246 | ## Styling and Design
247 |
248 | - **Framework:** Bootstrap is used for responsive design.
249 | - **Custom CSS:** Additional styles are added in the component-specific CSS files.
250 | - **Charting:** Chart.js is used for visualizing data in various components.
251 | - **Icons:** Icons are sourced from Font Awesome.
252 | - **Colors:** The color scheme is kept minimalistic with shades of blue and white.
253 | - **Typography:** The default font is set to Google Poppins.
254 | - **Layout:** The layout is designed to be user-friendly and intuitive.
255 | - **Responsive Design:** The application is optimized for various screen sizes.
256 | - **Animations:** Angular animations are used for transitions and effects.
257 |
258 | ## Troubleshooting
259 |
260 | ### Common Issues
261 |
262 | 1. **CORS Errors:**
263 | - Ensure your backend server is configured to accept requests from `http://localhost:4200`. This should have been set up already in the Django backend.
264 |
265 | 2. **Unauthorized Access Errors:**
266 | - Make sure the token is correctly stored in `localStorage` after login and is sent with each request that requires authorization.
267 |
268 | 3. **API Connection Issues:**
269 | - Verify that the backend server is running and accessible at `http://127.0.0.1:8000`.
270 |
271 | ### Debugging Tips
272 |
273 | - Use the browser's Developer Tools (F12) to inspect requests, responses, and console logs.
274 | - Check the network tab for failed requests and see the status codes and messages.
275 |
276 | ## Contributing
277 |
278 | Feel free to submit issues and contribute to the development of this project by opening a pull request.
279 |
280 | ## License
281 |
282 | This project is licensed under the [MIT License](LICENSE).
283 |
284 | ## Contact
285 |
286 | If you have any questions or need further assistance, feel free to contact me at [hoangson091104@gmail.com](mailto:hoangson091104@gmail.com).
287 |
288 | ---
289 |
290 | **[⬆ Back to Top](#e-learning-management-system-frontend)**
291 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/.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 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
3 | "recommendations": ["angular.ng-template"]
4 | }
5 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "ng serve",
7 | "type": "chrome",
8 | "request": "launch",
9 | "preLaunchTask": "npm: start",
10 | "url": "http://localhost:4200/"
11 | },
12 | {
13 | "name": "ng test",
14 | "type": "chrome",
15 | "request": "launch",
16 | "preLaunchTask": "npm: test",
17 | "url": "http://localhost:9876/debug.html"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
3 | "version": "2.0.0",
4 | "tasks": [
5 | {
6 | "type": "npm",
7 | "script": "start",
8 | "isBackground": true,
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "pattern": "$tsc",
12 | "background": {
13 | "activeOnStart": true,
14 | "beginsPattern": {
15 | "regexp": "(.*?)"
16 | },
17 | "endsPattern": {
18 | "regexp": "bundle generation complete"
19 | }
20 | }
21 | }
22 | },
23 | {
24 | "type": "npm",
25 | "script": "test",
26 | "isBackground": true,
27 | "problemMatcher": {
28 | "owner": "typescript",
29 | "pattern": "$tsc",
30 | "background": {
31 | "activeOnStart": true,
32 | "beginsPattern": {
33 | "regexp": "(.*?)"
34 | },
35 | "endsPattern": {
36 | "regexp": "bundle generation complete"
37 | }
38 | }
39 | }
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/README.md:
--------------------------------------------------------------------------------
1 | # E-Learning Management System Frontend
2 |
3 | The **E-Learning Management System Frontend** is the frontend component of the E-Learning Management System, a web-based application that allows administrators and users to interact with the e-learning platform's various components. The frontend is developed with Angular, and the backend is built using Django REST Framework.
4 |
5 | ## Table of Contents
6 |
7 | - [Project Overview](#project-overview)
8 | - [Features](#features)
9 | - [User Interfaces](#user-interfaces)
10 | - [File Structure](#file-structure)
11 | - [Getting Started](#getting-started)
12 | - [Prerequisites](#prerequisites)
13 | - [Backend Setup (Django)](#backend-setup)
14 | - [Frontend Setup (Angular)](#frontend-setup)
15 | - [Styling and Design](#styling-and-design)
16 | - [Troubleshooting](#troubleshooting)
17 | - [Contributing](#contributing)
18 | - [License](#license)
19 | - [Contact](#contact)
20 |
21 | ## Project Overview
22 |
23 | This frontend project is built with Angular, a popular web application framework developed by Google. It provides a user-friendly interface for managing courses, lessons, users, enrollments, and progress tracking. The frontend interacts with the backend APIs defined by Django to fetch and update data, handle user authentication, and display dynamic content.
24 |
25 | ## Features
26 |
27 | - **User Authentication**: Token-based login system.
28 | - **Course Management**: Display, create, update, and delete courses.
29 | - **Lesson Management**: Display, create, update, and delete lessons.
30 | - **User Management**: List and manage users.
31 | - **Enrollment Management**: Handle course enrollments.
32 | - **Progress Tracking**: Monitor user progress across lessons.
33 | - **Responsive Design**: Optimized for various devices.
34 | - **Charts and Data Visualization**: Implemented with Chart.js for dynamic data visualization.
35 |
36 | ## User Interfaces
37 |
38 | **Home Page**:
39 |
40 |
41 |
42 |
43 |
44 | **Course List**:
45 |
46 |
47 |
48 |
49 |
50 | **Lesson List**:
51 |
52 |
53 |
54 |
55 |
56 | **User List**:
57 |
58 |
59 |
60 |
61 |
62 | **Enrollment List**:
63 |
64 |
65 |
66 |
67 |
68 | **Progress List**:
69 |
70 |
71 |
72 |
73 |
74 | **Login Page**:
75 |
76 |
77 |
78 |
79 |
80 | ## File Structure
81 |
82 | The repository is organized as follows:
83 |
84 | ```plaintext
85 | Learning-Management-System/
86 | ├── LMS-Backend
87 | │ ├── (Backend code)
88 | │ └── ...
89 | ├── LMS-Frontend
90 | │ ├── angular.json
91 | │ ├── package.json
92 | │ ├── README.md
93 | │ ├── LICENSE
94 | │ ├── app/
95 | │ │ ├── src/
96 | │ │ │ ├── app/
97 | │ │ │ │ ├── auth/
98 | │ │ │ │ │ ├── login/
99 | │ │ │ │ │ │ ├── login.component.ts
100 | │ │ │ │ │ │ ├── login.component.html
101 | │ │ │ │ │ │ └── login.component.css
102 | │ │ │ │ ├── core/
103 | │ │ │ │ │ ├── footer/
104 | │ │ │ │ │ │ ├── footer.component.ts
105 | │ │ │ │ │ │ ├── footer.component.html
106 | │ │ │ │ │ │ └── footer.component.css
107 | │ │ │ │ │ ├── header/
108 | │ │ │ │ │ │ ├── header.component.ts
109 | │ │ │ │ │ │ ├── header.component.html
110 | │ │ │ │ │ │ └── header.component.css
111 | │ │ │ │ ├── pages/
112 | │ │ │ │ │ ├── home/
113 | │ │ │ │ │ │ ├── home.component.ts
114 | │ │ │ │ │ │ ├── home.component.html
115 | │ │ │ │ │ │ └── home.component.css
116 | │ │ │ │ ├── components/
117 | │ │ │ │ │ ├── course-list/
118 | │ │ │ │ │ │ ├── course-list.component.ts
119 | │ │ │ │ │ │ ├── course-list.component.html
120 | │ │ │ │ │ │ └── course-list.component.css
121 | │ │ │ │ │ ├── lesson-list/
122 | │ │ │ │ │ │ ├── lesson-list.component.ts
123 | │ │ │ │ │ │ ├── lesson-list.component.html
124 | │ │ │ │ │ │ └── lesson-list.component.css
125 | │ │ │ │ │ ├── user-list/
126 | │ │ │ │ │ │ ├── user-list.component.ts
127 | │ │ │ │ │ │ ├── user-list.component.html
128 | │ │ │ │ │ │ └── user-list.component.css
129 | │ │ │ │ │ ├── enrollment-list/
130 | │ │ │ │ │ │ ├── enrollment-list.component.ts
131 | │ │ │ │ │ │ ├── enrollment-list.component.html
132 | │ │ │ │ │ │ └── enrollment-list.component.css
133 | │ │ │ │ │ ├── progress-list/
134 | │ │ │ │ │ │ ├── progress-list.component.ts
135 | │ │ │ │ │ │ ├── progress-list.component.html
136 | │ │ │ │ │ │ └── progress-list.component.css
137 | │ │ │ │ ├── services/
138 | │ │ │ │ │ ├── auth.interceptor.ts
139 | │ │ │ │ │ ├── auth.service.ts
140 | │ │ │ │ │ ├── user.service.ts
141 | │ │ │ │ │ ├── course.service.ts
142 | │ │ │ │ │ ├── lesson.service.ts
143 | │ │ │ │ │ ├── enrollment.service.ts
144 | │ │ │ │ │ └── progress.service.ts
145 | │ │ │ │ ├── app.routes.ts
146 | │ │ │ │ ├── app.component.ts
147 | │ │ │ │ ├── app.config.ts
148 | │ │ │ │ ├── app.config.service.ts
149 | │ │ │ │ ├── app.component.html
150 | │ │ │ │ └── app.component.css
151 | │ │ │ ├── assets/
152 | │ │ │ │ └── images/
153 | │ │ │ │ └── .gitkeep
154 | │ │ │ ├── main.ts
155 | │ │ │ ├── styles.css
156 | │ │ │ └── index.html
157 | │ │ ├── .editorconfig
158 | │ │ ├── .gitignore
159 | │ │ ├── angular.json
160 | │ │ ├── package.json
161 | │ │ ├── package-lock.json
162 | │ │ ├── tsconfig.json
163 | │ │ ├── tsconfig.app.json
164 | │ │ └── tsconfig.spec.json
165 | │ ├── LICENSE
166 | │ ├── README.md
167 | ├── .gitignore
168 | ├── LICENSE
169 | └── README.md
170 | ```
171 |
172 | ## Getting Started
173 |
174 | ### Prerequisites
175 |
176 | Make sure you have the following installed on your machine:
177 |
178 | - **Node.js** (v18.19 or later)
179 | - **Angular CLI** (v12 or later)
180 | - **Python** (v3.7 or later)
181 |
182 | To check if you have these installed, run:
183 |
184 | ```bash
185 | node -v
186 | npm -v
187 | ng version
188 | python --version
189 | ```
190 |
191 | ### Backend Setup
192 |
193 | It is of utmost importance to set up the backend server before running the frontend application.
194 |
195 | 1. **Navigate to the backend directory:**
196 |
197 | ```bash
198 | cd Learning-Management-System/LMS-Backend
199 | ```
200 |
201 | 2. **Create a virtual environment:**
202 |
203 | ```bash
204 | python -m venv venv
205 | source venv/bin/activate # On Windows, use `venv\Scripts\activate`
206 | ```
207 |
208 | 3. **Install dependencies:**
209 |
210 | ```bash
211 | pip install -r requirements.txt
212 | ```
213 |
214 | 4. **Run the backend server:**
215 |
216 | ```bash
217 | python manage.py runserver
218 | ```
219 |
220 | ### Frontend Setup
221 |
222 | 1. **Navigate to the frontend directory:**
223 |
224 | ```bash
225 | cd Learning-Management-System/LMS-Frontend/app
226 | ```
227 |
228 | 2. **Install dependencies:**
229 |
230 | ```bash
231 | npm install
232 | ```
233 |
234 | 3. **Start the development server:**
235 |
236 | ```bash
237 | ng serve
238 | ```
239 |
240 | 4. **Open your browser and navigate to:**
241 |
242 | ```
243 | http://localhost:4200
244 | ```
245 |
246 | ## Styling and Design
247 |
248 | - **Framework:** Bootstrap is used for responsive design.
249 | - **Custom CSS:** Additional styles are added in the component-specific CSS files.
250 | - **Charting:** Chart.js is used for visualizing data in various components.
251 | - **Icons:** Icons are sourced from Font Awesome.
252 | - **Colors:** The color scheme is kept minimalistic with shades of blue and white.
253 | - **Typography:** The default font is set to Google Poppins.
254 | - **Layout:** The layout is designed to be user-friendly and intuitive.
255 | - **Responsive Design:** The application is optimized for various screen sizes.
256 | - **Animations:** Angular animations are used for transitions and effects.
257 |
258 | ## Troubleshooting
259 |
260 | ### Common Issues
261 |
262 | 1. **CORS Errors:**
263 | - Ensure your backend server is configured to accept requests from `http://localhost:4200`. This should have been set up already in the Django backend.
264 |
265 | 2. **Unauthorized Access Errors:**
266 | - Make sure the token is correctly stored in `localStorage` after login and is sent with each request that requires authorization.
267 |
268 | 3. **API Connection Issues:**
269 | - Verify that the backend server is running and accessible at `http://127.0.0.1:8000`.
270 |
271 | ### Debugging Tips
272 |
273 | - Use the browser's Developer Tools (F12) to inspect requests, responses, and console logs.
274 | - Check the network tab for failed requests and see the status codes and messages.
275 |
276 | ## Contributing
277 |
278 | Feel free to submit issues and contribute to the development of this project by opening a pull request.
279 |
280 | ## License
281 |
282 | This project is licensed under the [MIT License](LICENSE).
283 |
284 | ## Contact
285 |
286 | If you have any questions or need further assistance, feel free to contact me at [hoangson091104@gmail.com](mailto:hoangson091104@gmail.com).
287 |
288 | ---
289 |
290 | **[⬆ Back to Top](#e-learning-management-system-frontend)**
291 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "app": {
7 | "projectType": "application",
8 | "schematics": {
9 | "@schematics/angular:component": {
10 | "standalone": true
11 | },
12 | "@schematics/angular:directive": {
13 | "standalone": true
14 | },
15 | "@schematics/angular:pipe": {
16 | "standalone": true
17 | }
18 | },
19 | "root": "",
20 | "sourceRoot": "src",
21 | "prefix": "app",
22 | "architect": {
23 | "build": {
24 | "builder": "@angular-devkit/build-angular:browser",
25 | "options": {
26 | "outputPath": "dist/app",
27 | "index": "src/index.html",
28 | "main": "src/main.ts",
29 | "polyfills": [
30 | "zone.js"
31 | ],
32 | "tsConfig": "tsconfig.app.json",
33 | "assets": [
34 | "src/favicon.ico",
35 | "src/manifest.json",
36 | "src/assets"
37 | ],
38 | "styles": [
39 | "src/styles.css",
40 | "node_modules/bootstrap/dist/css/bootstrap.min.css"
41 | ],
42 | "scripts": [
43 | "node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
44 | ]
45 | },
46 | "configurations": {
47 | "production": {
48 | "budgets": [
49 | {
50 | "type": "initial",
51 | "maximumWarning": "500kb",
52 | "maximumError": "1mb"
53 | },
54 | {
55 | "type": "anyComponentStyle",
56 | "maximumWarning": "2kb",
57 | "maximumError": "4kb"
58 | }
59 | ],
60 | "outputHashing": "all"
61 | },
62 | "development": {
63 | "buildOptimizer": false,
64 | "optimization": false,
65 | "vendorChunk": true,
66 | "extractLicenses": false,
67 | "sourceMap": true,
68 | "namedChunks": true
69 | }
70 | },
71 | "defaultConfiguration": "production"
72 | },
73 | "serve": {
74 | "builder": "@angular-devkit/build-angular:dev-server",
75 | "configurations": {
76 | "production": {
77 | "browserTarget": "app:build:production"
78 | },
79 | "development": {
80 | "browserTarget": "app:build:development"
81 | }
82 | },
83 | "defaultConfiguration": "development"
84 | },
85 | "extract-i18n": {
86 | "builder": "@angular-devkit/build-angular:extract-i18n",
87 | "options": {
88 | "browserTarget": "app:build"
89 | }
90 | },
91 | "test": {
92 | "builder": "@angular-devkit/build-angular:karma",
93 | "options": {
94 | "polyfills": [
95 | "zone.js",
96 | "zone.js/testing"
97 | ],
98 | "tsConfig": "tsconfig.spec.json",
99 | "assets": [
100 | "src/favicon.ico",
101 | "src/assets"
102 | ],
103 | "styles": [
104 | "src/styles.css"
105 | ],
106 | "scripts": []
107 | }
108 | }
109 | }
110 | }
111 | },
112 | "cli": {
113 | "analytics": false
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "elearning-frontend",
3 | "displayName": "E-Learning Management System Frontend",
4 | "description": "This is the frontend of the E-Learning Management System",
5 | "author": "Son Nguyen",
6 | "license": "MIT",
7 | "repository": {
8 | "type": "git",
9 | "url": "git+https://github.com/hoangsonww/Learning-Management-System.git"
10 | },
11 | "bugs": {
12 | "url": "https://github.com/hoangsonww/Learning-Management-System/issues"
13 | },
14 | "version": "1.1.0",
15 | "scripts": {
16 | "ng": "ng",
17 | "start": "ng serve",
18 | "build": "ng build",
19 | "watch": "ng build --watch --configuration development",
20 | "test": "ng test"
21 | },
22 | "private": true,
23 | "dependencies": {
24 | "@angular/animations": "^16.0.0",
25 | "@angular/common": "^16.0.0",
26 | "@angular/compiler": "^16.0.0",
27 | "@angular/core": "^16.0.0",
28 | "@angular/forms": "^16.0.0",
29 | "@angular/platform-browser": "^16.0.0",
30 | "@angular/platform-browser-dynamic": "^16.0.0",
31 | "@angular/router": "^16.0.0",
32 | "bootstrap": "^5.3.3",
33 | "chart.js": "^4.4.4",
34 | "rxjs": "~7.8.0",
35 | "tslib": "^2.3.0",
36 | "zone.js": "~0.13.0"
37 | },
38 | "devDependencies": {
39 | "@angular-devkit/build-angular": "^16.0.4",
40 | "@angular/cli": "~16.0.4",
41 | "@angular/compiler-cli": "^16.0.0",
42 | "@types/jasmine": "~4.3.0",
43 | "jasmine-core": "~4.6.0",
44 | "karma": "~6.4.0",
45 | "karma-chrome-launcher": "~3.2.0",
46 | "karma-coverage": "~2.2.0",
47 | "karma-jasmine": "~5.1.0",
48 | "karma-jasmine-html-reporter": "~2.0.0",
49 | "typescript": "~5.0.2"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Frontend/app/src/android-chrome-192x192.png
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Frontend/app/src/android-chrome-512x512.png
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/app.component.css:
--------------------------------------------------------------------------------
1 | .container {
2 | margin-top: 20px;
3 | }
4 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { RouterModule } from '@angular/router';
3 | import { HeaderComponent } from './core/header/header.component';
4 | import { FooterComponent } from './core/footer/footer.component';
5 |
6 | @Component({
7 | selector: 'app-root',
8 | standalone: true,
9 | imports: [RouterModule, HeaderComponent, FooterComponent],
10 | template: `
11 |
12 |
13 |
14 |
15 |
16 | `,
17 | styles: [
18 | `
19 | .container {
20 | margin-top: 20px;
21 | padding: 15px;
22 | max-width: 1200px;
23 | margin-left: auto;
24 | margin-right: auto;
25 | }
26 | `,
27 | ],
28 | })
29 | export class AppComponent {}
30 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/app.config.server.ts:
--------------------------------------------------------------------------------
1 | import { ApplicationConfig, importProvidersFrom } from '@angular/core';
2 | import { provideHttpClient, HTTP_INTERCEPTORS } from '@angular/common/http';
3 | import { AuthInterceptor } from './services/auth.interceptor';
4 |
5 | export const config: ApplicationConfig = {
6 | providers: [
7 | provideHttpClient(),
8 | { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
9 | importProvidersFrom([]),
10 | ],
11 | };
12 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/app.config.ts:
--------------------------------------------------------------------------------
1 | import { ApplicationConfig } from '@angular/core';
2 | import { provideRouter } from '@angular/router';
3 | import { provideHttpClient, withFetch } from '@angular/common/http';
4 | import { routes } from './app.routes';
5 | import { provideClientHydration } from '@angular/platform-browser';
6 |
7 | export const appConfig: ApplicationConfig = {
8 | providers: [
9 | provideRouter(routes),
10 | provideHttpClient(withFetch()),
11 | provideClientHydration(),
12 | ],
13 | };
14 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/app.routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 | import { HomeComponent } from './pages/home/home.component';
3 | import { LoginComponent } from './auth/login/login.component';
4 | import { UserListComponent } from './components/user-list/user-list.component';
5 | import { CourseListComponent } from './components/course-list/course-list.component';
6 | import { LessonListComponent } from './components/lesson-list/lesson-list.component';
7 | import { EnrollmentListComponent } from './components/enrollment-list/enrollment-list.component';
8 | import { ProgressListComponent } from './components/progress-list/progress-list.component';
9 | import { RegisterComponent } from './auth/register/register.component';
10 | import { NotFoundComponent } from './pages/notfound/notfound.component';
11 |
12 | export const routes: Routes = [
13 | { path: '', component: HomeComponent },
14 | { path: 'login', component: LoginComponent },
15 | { path: 'register', component: RegisterComponent },
16 | { path: 'users', component: UserListComponent },
17 | { path: 'courses', component: CourseListComponent },
18 | { path: 'lessons', component: LessonListComponent },
19 | { path: 'enrollments', component: EnrollmentListComponent },
20 | { path: 'progress', component: ProgressListComponent },
21 | { path: '**', component: NotFoundComponent },
22 | ];
23 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/auth/login/login.component.css:
--------------------------------------------------------------------------------
1 | .login-container {
2 | min-height: 80vh;
3 | display: flex;
4 | align-items: center;
5 | justify-content: center;
6 | margin-top: 0;
7 | }
8 |
9 | .form-label {
10 | font-size: 1.1rem;
11 | font-weight: 500;
12 | margin-bottom: 0.5rem;
13 | color: #333;
14 | margin-right: 10px;
15 | }
16 |
17 | .form-control-lg {
18 | height: calc(2rem + 2px);
19 | padding: 0.75rem 1rem;
20 | border-radius: 8px;
21 | border: 1px solid #ccc;
22 | transition: border-color 0.3s ease;
23 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
24 | margin-top: 5px;
25 | font: inherit;
26 | }
27 |
28 | .form-control-lg:focus {
29 | border-color: #007bff;
30 | box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
31 | }
32 |
33 | .login-card {
34 | max-width: 400px;
35 | width: 100%;
36 | border-radius: 12px;
37 | border: none;
38 | box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
39 | padding: 20px;
40 | }
41 |
42 | .btn-primary {
43 | font-weight: 600;
44 | padding: 0.75rem 1rem;
45 | font-size: 1rem;
46 | transition:
47 | background-color 0.3s,
48 | transform 0.3s;
49 | border-radius: 8px;
50 | }
51 |
52 | .btn-primary:hover {
53 | background-color: #0056b3;
54 | transform: scale(1.05);
55 | }
56 |
57 | .alert-danger {
58 | font-size: 0.95rem;
59 | padding: 12px;
60 | border-radius: 8px;
61 | }
62 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/auth/login/login.component.html:
--------------------------------------------------------------------------------
1 |
57 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/auth/login/login.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { Router } from '@angular/router';
3 | import { AuthService } from '../../services/auth.service';
4 | import { FormsModule } from '@angular/forms';
5 | import { CommonModule } from '@angular/common';
6 |
7 | @Component({
8 | selector: 'app-login',
9 | standalone: true,
10 | imports: [FormsModule, CommonModule],
11 | templateUrl: './login.component.html',
12 | styleUrls: ['./login.component.css'],
13 | })
14 | export class LoginComponent {
15 | username: string = '';
16 | password: string = '';
17 | errorMessage: string = '';
18 | isLoading: boolean = false; // New flag to manage loading state
19 |
20 | constructor(
21 | private authService: AuthService,
22 | private router: Router,
23 | ) {}
24 |
25 | login(): void {
26 | // Reset error message before login attempt
27 | this.errorMessage = '';
28 |
29 | // Set loading state to true
30 | this.isLoading = true;
31 |
32 | this.authService.login(this.username, this.password).subscribe(
33 | () => {
34 | // Reset loading state on success
35 | this.isLoading = false;
36 | this.router.navigate(['/']); // Redirect to home page after successful login
37 | },
38 | (error) => {
39 | // Reset loading state and show error on failure
40 | this.isLoading = false;
41 | console.log(error);
42 | this.errorMessage =
43 | error.error?.detail ||
44 | 'Invalid username or password. Please try registering first.';
45 | },
46 | );
47 | }
48 |
49 | logout(): void {
50 | this.authService.logout();
51 | this.router.navigate(['/login']);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/auth/register/register.component.css:
--------------------------------------------------------------------------------
1 | .register-container {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | margin-top: 0;
6 | }
7 |
8 | .form-label {
9 | font-size: 1.1rem;
10 | font-weight: 500;
11 | margin-bottom: 0.5rem;
12 | color: #333;
13 | margin-right: 10px;
14 | }
15 |
16 | .form-control-lg {
17 | height: calc(2rem + 2px);
18 | padding: 0.75rem 1rem;
19 | border-radius: 8px;
20 | border: 1px solid #ccc;
21 | transition: border-color 0.3s ease;
22 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
23 | margin-top: 5px;
24 | font: inherit;
25 | }
26 |
27 | .form-control-lg:focus {
28 | border-color: #007bff;
29 | box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
30 | }
31 |
32 | .register-card {
33 | max-width: 400px;
34 | width: 100%;
35 | border-radius: 12px;
36 | border: none;
37 | box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
38 | padding: 20px;
39 | }
40 |
41 | .btn-primary {
42 | font-weight: 600;
43 | padding: 0.75rem 1rem;
44 | font-size: 1rem;
45 | transition:
46 | background-color 0.3s,
47 | transform 0.3s;
48 | border-radius: 8px;
49 | }
50 |
51 | .btn-primary:hover {
52 | background-color: #0056b3;
53 | transform: scale(1.05);
54 | }
55 |
56 | .alert-danger {
57 | font-size: 0.95rem;
58 | padding: 12px;
59 | border-radius: 8px;
60 | }
61 |
62 | a {
63 | color: #007bff !important;
64 | text-decoration: none;
65 | cursor: pointer;
66 | }
67 |
68 | a:hover {
69 | text-decoration: underline !important;
70 | }
71 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/auth/register/register.component.html:
--------------------------------------------------------------------------------
1 |
127 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/auth/register/register.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { Router } from '@angular/router';
3 | import { AuthService } from '../../services/auth.service';
4 | import { FormsModule } from '@angular/forms';
5 | import { CommonModule } from '@angular/common';
6 |
7 | @Component({
8 | selector: 'app-register',
9 | standalone: true,
10 | imports: [FormsModule, CommonModule],
11 | templateUrl: './register.component.html',
12 | styleUrls: ['./register.component.css'],
13 | })
14 | export class RegisterComponent {
15 | username: string = '';
16 | email: string = '';
17 | password1: string = '';
18 | password2: string = '';
19 | errorMessage: string = '';
20 | passwordError: string | null = null;
21 | isLoading: boolean = false; // New flag to manage loading state
22 |
23 | constructor(
24 | private authService: AuthService,
25 | private router: Router,
26 | ) {}
27 |
28 | /**
29 | * This function ensures the password is not purely numeric and also
30 | * provides error messages for form validation.
31 | */
32 | validatePassword(): void {
33 | const isNumeric = /^\d+$/.test(this.password1);
34 | if (isNumeric) {
35 | this.passwordError = 'Password cannot be entirely numeric.';
36 | } else {
37 | this.passwordError = null; // Reset if valid
38 | }
39 | }
40 |
41 | goToLogin(): void {
42 | this.router.navigate(['/login']);
43 | }
44 |
45 | register(): void {
46 | this.errorMessage = '';
47 |
48 | // Validate password match
49 | if (this.password1 !== this.password2) {
50 | this.errorMessage = 'Passwords do not match.';
51 | return;
52 | }
53 |
54 | if (this.passwordError) {
55 | return;
56 | }
57 |
58 | // Set loading state to true before making the request
59 | this.isLoading = true;
60 |
61 | this.authService
62 | .register(this.username, this.email, this.password1, this.password2)
63 | .subscribe(
64 | () => {
65 | // Reset loading state on success
66 | this.isLoading = false;
67 | this.router.navigate(['/login']); // Redirect to login page after successful registration
68 | },
69 | (error) => {
70 | // Reset loading state on failure
71 | this.isLoading = false;
72 |
73 | // Handle and display specific error messages from the backend
74 | if (error.error) {
75 | if (error.error.username) {
76 | this.errorMessage = error.error.username[0]; // Display the first error message related to username
77 | } else if (error.error.email) {
78 | this.errorMessage = error.error.email[0]; // Display the first error message related to email
79 | } else if (error.error.password1) {
80 | this.errorMessage = error.error.password1[0]; // Display the first error message related to password1
81 | } else if (error.error.non_field_errors) {
82 | this.errorMessage = error.error.non_field_errors[0]; // Display general error messages
83 | } else {
84 | this.errorMessage = 'Registration failed. Please try again.';
85 | }
86 | } else {
87 | this.errorMessage = 'An unknown error occurred. Please try again.';
88 | }
89 | },
90 | );
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/components/course-list/course-list.component.css:
--------------------------------------------------------------------------------
1 | .container {
2 | max-width: 1200px;
3 | margin: auto;
4 | }
5 |
6 | .chart-section {
7 | margin-bottom: 20px;
8 | display: flex;
9 | justify-content: center;
10 | margin-top: 20px;
11 | }
12 |
13 | .chart-container {
14 | width: 300px;
15 | height: 300px;
16 | }
17 |
18 | .card {
19 | border: none;
20 | transition:
21 | transform 0.3s,
22 | box-shadow 0.3s;
23 | padding: 15px;
24 | }
25 |
26 | .card:hover {
27 | transform: translateY(-5px);
28 | box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
29 | }
30 |
31 | .card-title {
32 | font-size: 1.5rem;
33 | font-weight: 700;
34 | margin-bottom: 10px;
35 | }
36 |
37 | .card-text {
38 | color: #666;
39 | font-size: 0.95rem;
40 | }
41 |
42 | .rounded-custom {
43 | border-radius: 15px;
44 | margin: 15px;
45 | }
46 |
47 | .loading-overlay {
48 | position: fixed; /* Cover the entire viewport */
49 | top: 0;
50 | left: 0;
51 | width: 100vw;
52 | height: 100vh;
53 | display: flex;
54 | justify-content: center;
55 | align-items: center;
56 | background-color: rgba(
57 | 255,
58 | 255,
59 | 255,
60 | 0.8
61 | ); /* Optional: light overlay effect */
62 | z-index: 9999; /* Ensure it stays on top */
63 | }
64 |
65 | .spinner-border {
66 | width: 3rem;
67 | height: 3rem;
68 | }
69 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/components/course-list/course-list.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Platform Overview
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 | Loading...
15 |
16 |
17 |
18 |
19 |
20 |
Courses
21 |
26 | {{ errorMessage }}
27 |
28 |
29 | No courses available.
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
{{ course.title }}
39 |
{{ course.description }}
40 |
41 |
42 | Instructor:
43 | {{ formatInstructorName(course.instructor) }}
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/components/course-list/course-list.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { CourseService } from '../../services/course.service';
3 | import { HttpClient, HttpHeaders } from '@angular/common/http';
4 | import { CommonModule } from '@angular/common';
5 | import { Chart, ChartConfiguration, registerables } from 'chart.js';
6 | import { forkJoin } from 'rxjs';
7 |
8 | Chart.register(...registerables);
9 |
10 | @Component({
11 | selector: 'app-course-list',
12 | standalone: true,
13 | imports: [CommonModule],
14 | templateUrl: './course-list.component.html',
15 | styleUrls: ['./course-list.component.css'],
16 | })
17 | export class CourseListComponent implements OnInit {
18 | courses: any[] = [];
19 | errorMessage: string = '';
20 | loading: boolean = true; // Track loading state
21 | chart: Chart<'pie'> | undefined;
22 | private apiUrl =
23 | 'https://learning-management-system-fullstack.onrender.com/api/';
24 | lessonsLength: number = 0;
25 | enrollmentsLength: number = 0;
26 | isAuthenticated: boolean = true; // Flag to check authentication status
27 |
28 | constructor(
29 | private courseService: CourseService,
30 | private http: HttpClient,
31 | ) {}
32 |
33 | ngOnInit(): void {
34 | this.fetchAllData();
35 | }
36 |
37 | fetchAllData(): void {
38 | const token = localStorage.getItem('authToken');
39 | const headers = new HttpHeaders().set('Authorization', `Token ${token}`);
40 |
41 | const courses$ = this.courseService.getCourses();
42 | const lessons$ = this.http.get(`${this.apiUrl}lessons/`, { headers });
43 | const enrollments$ = this.http.get(`${this.apiUrl}enrollments/`, {
44 | headers,
45 | });
46 |
47 | forkJoin([courses$, lessons$, enrollments$]).subscribe(
48 | ([coursesData, lessonsData, enrollmentsData]: [any[], any, any]) => {
49 | this.courses = coursesData;
50 | this.lessonsLength = lessonsData.length;
51 | this.enrollmentsLength = enrollmentsData.length;
52 | this.loading = false; // Stop loading after data fetching is complete
53 | if (this.isAuthenticated) {
54 | this.renderChart(); // Render the chart after data is fetched
55 | }
56 | },
57 | (error) => {
58 | this.loading = false; // Stop loading in case of error
59 | if (error.status === 401) {
60 | this.isAuthenticated = false; // Set flag to false on unauthorized access
61 | this.errorMessage = 'Unauthorized access. Please log in.';
62 | } else {
63 | this.errorMessage =
64 | 'Error fetching data due to expired token. Please try registering and logging in again.';
65 | }
66 | this.lessonsLength = 20;
67 | this.enrollmentsLength = 50;
68 | if (this.isAuthenticated) {
69 | this.renderChart(); // Ensure the chart is only rendered if authenticated
70 | }
71 | },
72 | );
73 | }
74 |
75 | formatInstructorName(instructor: any): string {
76 | return 'John Doe';
77 | }
78 |
79 | renderChart(): void {
80 | const ctx = document.getElementById('courseChart') as HTMLCanvasElement;
81 |
82 | if (this.chart) {
83 | this.chart.destroy(); // Destroy any existing chart instance before creating a new one
84 | }
85 |
86 | const chartConfig: ChartConfiguration<'pie'> = {
87 | type: 'pie',
88 | data: {
89 | labels: ['Courses', 'Lessons', 'Enrollments'],
90 | datasets: [
91 | {
92 | data: [
93 | this.courses.length,
94 | this.lessonsLength,
95 | this.enrollmentsLength,
96 | ],
97 | backgroundColor: ['#007bff', '#ffc107', '#28a745'],
98 | },
99 | ],
100 | },
101 | options: {
102 | responsive: true,
103 | plugins: {
104 | legend: {
105 | position: 'bottom',
106 | },
107 | },
108 | },
109 | };
110 |
111 | this.chart = new Chart(ctx, chartConfig);
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/components/enrollment-list/enrollment-list.component.css:
--------------------------------------------------------------------------------
1 | .chart-section {
2 | margin-bottom: 20px;
3 | display: flex;
4 | justify-content: center;
5 | margin-top: 20px;
6 | }
7 |
8 | .chart-container {
9 | width: 300px;
10 | height: 300px;
11 | }
12 |
13 | .container {
14 | padding-top: 0;
15 | }
16 |
17 | .card {
18 | border: none;
19 | transition:
20 | transform 0.3s,
21 | box-shadow 0.3s;
22 | padding: 15px;
23 | }
24 |
25 | .card:hover {
26 | transform: translateY(-5px);
27 | box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
28 | }
29 |
30 | .card-title {
31 | font-size: 1.5rem;
32 | font-weight: 700;
33 | margin-bottom: 10px;
34 | }
35 |
36 | .card-text {
37 | color: #666;
38 | font-size: 0.95rem;
39 | }
40 |
41 | .rounded-custom {
42 | border-radius: 15px;
43 | margin: 15px;
44 | }
45 |
46 | .loading-overlay {
47 | position: fixed; /* Cover the entire viewport */
48 | top: 0;
49 | left: 0;
50 | width: 100vw;
51 | height: 100vh;
52 | display: flex;
53 | justify-content: center;
54 | align-items: center;
55 | background-color: rgba(
56 | 255,
57 | 255,
58 | 255,
59 | 0.8
60 | ); /* Optional: light overlay effect */
61 | z-index: 9999; /* Ensure it stays on top */
62 | }
63 |
64 | .spinner-border {
65 | width: 3rem;
66 | height: 3rem;
67 | }
68 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/components/enrollment-list/enrollment-list.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Platform Overview
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 | Loading...
15 |
16 |
17 |
18 |
19 |
20 |
Enrollment List
21 |
22 | {{ errorMessage }}
23 |
24 |
28 | No enrollments available.
29 |
30 |
31 |
32 |
33 |
37 |
38 |
39 |
40 |
41 | User: {{ enrollment.student }}
42 |
43 |
44 | Course: {{ enrollment.course }}
45 |
46 |
47 |
48 |
49 | Date Enrolled:
50 | {{ enrollment.enrolled_at | date: "fullDate" }}
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/components/enrollment-list/enrollment-list.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { EnrollmentService } from '../../services/enrollment.service';
3 | import { HttpClient, HttpHeaders } from '@angular/common/http';
4 | import { CommonModule } from '@angular/common';
5 | import { Chart, ChartConfiguration, registerables } from 'chart.js';
6 | import { forkJoin } from 'rxjs';
7 |
8 | interface Enrollment {
9 | _id: string;
10 | student: string;
11 | course: string;
12 | enrolled_at: string;
13 | }
14 |
15 | Chart.register(...registerables);
16 |
17 | @Component({
18 | selector: 'app-enrollment-list',
19 | standalone: true,
20 | imports: [CommonModule],
21 | templateUrl: './enrollment-list.component.html',
22 | styleUrls: ['./enrollment-list.component.css'],
23 | })
24 | export class EnrollmentListComponent implements OnInit {
25 | enrollments: Enrollment[] = [];
26 | errorMessage: string = '';
27 | loading: boolean = true; // Track loading state
28 | chart: Chart<'pie'> | undefined;
29 | private apiUrl =
30 | 'https://learning-management-system-fullstack.onrender.com/api/';
31 | coursesLength: number = 0;
32 | lessonsLength: number = 0;
33 | isAuthenticated: boolean = true; // Flag to check authentication status
34 |
35 | constructor(
36 | private enrollmentService: EnrollmentService,
37 | private http: HttpClient,
38 | ) {}
39 |
40 | ngOnInit(): void {
41 | this.fetchAllData();
42 | }
43 |
44 | fetchAllData(): void {
45 | const token = localStorage.getItem('authToken');
46 | const headers = new HttpHeaders().set('Authorization', `Token ${token}`);
47 |
48 | const enrollments$ = this.enrollmentService.getEnrollments();
49 | const courses$ = this.http.get(`${this.apiUrl}courses/`, { headers });
50 | const lessons$ = this.http.get(`${this.apiUrl}lessons/`, { headers });
51 |
52 | forkJoin([enrollments$, courses$, lessons$]).subscribe(
53 | ([enrollmentsData, coursesData, lessonsData]: [
54 | Enrollment[],
55 | any,
56 | any,
57 | ]) => {
58 | this.enrollments = enrollmentsData;
59 | this.coursesLength = coursesData.length;
60 | this.lessonsLength = lessonsData.length;
61 | this.fetchUserDetails(); // Fetch user details based on student ID
62 | },
63 | (error) => {
64 | this.loading = false; // Stop loading in case of error
65 | if (error.status === 401) {
66 | this.isAuthenticated = false; // Set flag to false on unauthorized access
67 | this.errorMessage = 'Unauthorized access. Please log in.';
68 | } else {
69 | this.errorMessage =
70 | 'Error fetching data due to expired token. Please try registering and logging in again.';
71 | }
72 | this.coursesLength = 10; // Fallback values in case of error
73 | this.lessonsLength = 10;
74 | },
75 | );
76 | }
77 |
78 | fetchUserDetails(): void {
79 | const token = localStorage.getItem('authToken');
80 | const headers = new HttpHeaders().set('Authorization', `Token ${token}`);
81 |
82 | // Fetch the entire list of users and match by ID
83 | const userDetailsRequest = this.http.get(`${this.apiUrl}users/`, {
84 | headers,
85 | });
86 |
87 | userDetailsRequest.subscribe(
88 | (userList: any) => {
89 | if (Array.isArray(userList)) {
90 | this.enrollments.forEach((enrollment) => {
91 | const matchedUser = userList.find(
92 | (user: any) => user.id === enrollment.student,
93 | );
94 | if (matchedUser) {
95 | enrollment.student = matchedUser.username;
96 | }
97 | });
98 | this.fetchCourseTitles(); // Fetch course titles after fetching user details
99 | }
100 | },
101 | (error) => {
102 | this.loading = false; // Stop loading even in case of error
103 | console.error('Error fetching user details', error);
104 | },
105 | );
106 | }
107 |
108 | fetchCourseTitles(): void {
109 | const token = localStorage.getItem('authToken');
110 | const headers = new HttpHeaders().set('Authorization', `Token ${token}`);
111 |
112 | const coursesRequest = this.http.get(`${this.apiUrl}courses/`, { headers });
113 |
114 | coursesRequest.subscribe(
115 | (courseList: any) => {
116 | if (Array.isArray(courseList)) {
117 | this.enrollments.forEach((enrollment) => {
118 | const matchedCourse = courseList.find(
119 | (course: any) => course.id === enrollment.course,
120 | );
121 | if (matchedCourse) {
122 | enrollment.course = matchedCourse.title;
123 | }
124 | });
125 | this.loading = false;
126 | if (this.isAuthenticated) {
127 | // Ensure the chart is only rendered if authenticated
128 | this.renderChart();
129 | }
130 | }
131 | },
132 | (error) => {
133 | this.loading = false; // Stop loading even in case of error
134 | console.error('Error fetching course details', error);
135 | },
136 | );
137 | }
138 |
139 | renderChart(): void {
140 | const ctx = document.getElementById('enrollmentChart') as HTMLCanvasElement;
141 |
142 | if (this.chart) {
143 | this.chart.destroy(); // Destroy any existing chart instance before creating a new one
144 | }
145 |
146 | const chartConfig: ChartConfiguration<'pie'> = {
147 | type: 'pie',
148 | data: {
149 | labels: ['Lessons', 'Courses', 'Enrollments'],
150 | datasets: [
151 | {
152 | data: [
153 | this.lessonsLength,
154 | this.coursesLength,
155 | this.enrollments.length,
156 | ],
157 | backgroundColor: ['#007bff', '#ffc107', '#28a745'],
158 | },
159 | ],
160 | },
161 | options: {
162 | responsive: true,
163 | plugins: {
164 | legend: {
165 | position: 'bottom',
166 | },
167 | },
168 | },
169 | };
170 |
171 | this.chart = new Chart(ctx, chartConfig);
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/components/lesson-list/lesson-list.component.css:
--------------------------------------------------------------------------------
1 | .container {
2 | max-width: 1200px;
3 | margin: auto;
4 | }
5 |
6 | .chart-section {
7 | margin-bottom: 20px;
8 | display: flex;
9 | justify-content: center;
10 | margin-top: 20px;
11 | }
12 |
13 | .chart-container {
14 | width: 300px;
15 | height: 300px;
16 | }
17 |
18 | .card {
19 | border: none;
20 | transition:
21 | transform 0.3s,
22 | box-shadow 0.3s;
23 | padding: 15px;
24 | }
25 |
26 | .card:hover {
27 | transform: translateY(-5px);
28 | box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
29 | }
30 |
31 | .loading-overlay {
32 | position: fixed; /* Cover the entire viewport */
33 | top: 0;
34 | left: 0;
35 | width: 100vw;
36 | height: 100vh;
37 | display: flex;
38 | justify-content: center;
39 | align-items: center;
40 | background-color: rgba(
41 | 255,
42 | 255,
43 | 255,
44 | 0.8
45 | ); /* Optional: light overlay effect */
46 | z-index: 9999; /* Ensure it stays on top */
47 | }
48 |
49 | .spinner-border {
50 | width: 3rem;
51 | height: 3rem;
52 | }
53 |
54 | .card-title {
55 | font-size: 1.5rem;
56 | font-weight: 700;
57 | margin-bottom: 10px;
58 | }
59 |
60 | .card-text {
61 | color: #666;
62 | font-size: 0.95rem;
63 | }
64 |
65 | .rounded-custom {
66 | border-radius: 15px;
67 | margin: 15px;
68 | }
69 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/components/lesson-list/lesson-list.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Platform Overview
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 | Loading...
15 |
16 |
17 |
18 |
19 |
20 |
Lessons
21 |
26 | {{ errorMessage }}
27 |
28 |
29 | No lessons available.
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
{{ lesson.title }}
39 |
{{ lesson.content }}
40 |
41 |
Duration: 75 minutes
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/components/lesson-list/lesson-list.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { LessonService } from '../../services/lesson.service';
3 | import { CommonModule } from '@angular/common';
4 | import { Chart, ChartConfiguration, registerables } from 'chart.js';
5 | import { HttpClient, HttpHeaders } from '@angular/common/http';
6 | import { forkJoin } from 'rxjs';
7 |
8 | Chart.register(...registerables);
9 |
10 | @Component({
11 | selector: 'app-lesson-list',
12 | standalone: true,
13 | imports: [CommonModule],
14 | templateUrl: './lesson-list.component.html',
15 | styleUrls: ['./lesson-list.component.css'],
16 | })
17 | export class LessonListComponent implements OnInit {
18 | lessons: any[] = [];
19 | errorMessage: string = '';
20 | loading: boolean = true; // Track loading state
21 | chart: Chart<'pie'> | undefined;
22 | private apiUrl =
23 | 'https://learning-management-system-fullstack.onrender.com/api/';
24 | enrollmentsLength: number = 0;
25 | coursesLength: number = 0;
26 |
27 | constructor(
28 | private lessonService: LessonService,
29 | private http: HttpClient,
30 | ) {}
31 |
32 | ngOnInit(): void {
33 | this.lessonService.getLessons().subscribe(
34 | (data) => {
35 | this.lessons = data;
36 | this.fetchAllData();
37 | },
38 | (error) => {
39 | this.loading = false; // Stop loading in case of error
40 | if (error.status === 401) {
41 | this.errorMessage = 'Unauthorized access. Please log in.';
42 | } else {
43 | this.errorMessage = 'Error fetching lessons.';
44 | }
45 | },
46 | );
47 | }
48 |
49 | fetchAllData(): void {
50 | const token = localStorage.getItem('authToken');
51 | const headers = new HttpHeaders().set('Authorization', `Token ${token}`);
52 |
53 | const enrollments$ = this.http.get(`${this.apiUrl}enrollments/`, {
54 | headers,
55 | });
56 | const courses$ = this.http.get(`${this.apiUrl}courses/`, { headers });
57 |
58 | forkJoin([enrollments$, courses$]).subscribe(
59 | ([enrollmentsData, coursesData]: [any, any]) => {
60 | this.enrollmentsLength = enrollmentsData.length;
61 | this.coursesLength = coursesData.length;
62 | this.loading = false; // Stop loading once data is fetched
63 | this.renderChart(); // Render the chart after data fetching is complete
64 | },
65 | (error) => {
66 | this.loading = false; // Stop loading even in case of error
67 | if (error.status === 401) {
68 | this.errorMessage = 'Unauthorized access. Please log in.';
69 | this.enrollmentsLength = 30;
70 | this.coursesLength = 10;
71 | } else {
72 | this.errorMessage =
73 | 'Error fetching data due to expired token. Please try registering and logging in again.';
74 | this.enrollmentsLength = 30;
75 | this.coursesLength = 10;
76 | }
77 | this.renderChart();
78 | },
79 | );
80 | }
81 |
82 | renderChart(): void {
83 | const ctx = document.getElementById('lessonChart') as HTMLCanvasElement;
84 |
85 | if (this.chart) {
86 | this.chart.destroy();
87 | }
88 |
89 | const chartConfig: ChartConfiguration<'pie'> = {
90 | type: 'pie',
91 | data: {
92 | labels: ['Lessons', 'Courses', 'Enrollments'],
93 | datasets: [
94 | {
95 | data: [
96 | this.lessons.length,
97 | this.coursesLength,
98 | this.enrollmentsLength,
99 | ],
100 | backgroundColor: ['#007bff', '#ffc107', '#28a745'],
101 | },
102 | ],
103 | },
104 | options: {
105 | responsive: true,
106 | plugins: {
107 | legend: {
108 | position: 'bottom',
109 | },
110 | },
111 | },
112 | };
113 |
114 | this.chart = new Chart(ctx, chartConfig);
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/components/progress-list/progress-list.component.css:
--------------------------------------------------------------------------------
1 | .container {
2 | max-width: 1200px;
3 | margin: auto;
4 | }
5 |
6 | .chart-section {
7 | margin-bottom: 20px;
8 | display: flex;
9 | justify-content: center;
10 | margin-top: 20px;
11 | }
12 |
13 | .chart-container {
14 | width: 300px;
15 | height: 300px;
16 | }
17 |
18 | .card {
19 | border: none;
20 | transition:
21 | transform 0.3s,
22 | box-shadow 0.3s;
23 | padding: 15px;
24 | }
25 |
26 | .card:hover {
27 | transform: translateY(-5px);
28 | box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
29 | }
30 |
31 | .card-title {
32 | font-size: 1.5rem;
33 | font-weight: 700;
34 | margin-bottom: 10px;
35 | }
36 |
37 | .card-text {
38 | color: #666;
39 | font-size: 0.95rem;
40 | }
41 |
42 | .rounded-custom {
43 | border-radius: 15px;
44 | margin: 15px;
45 | }
46 |
47 | .loading-overlay {
48 | position: fixed; /* Cover the entire viewport */
49 | top: 0;
50 | left: 0;
51 | width: 100vw;
52 | height: 100vh;
53 | display: flex;
54 | justify-content: center;
55 | align-items: center;
56 | background-color: rgba(
57 | 255,
58 | 255,
59 | 255,
60 | 0.8
61 | ); /* Optional: light overlay effect */
62 | z-index: 9999; /* Ensure it stays on top */
63 | }
64 |
65 | .spinner-border {
66 | width: 3rem;
67 | height: 3rem;
68 | }
69 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/components/progress-list/progress-list.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Platform Overview
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 | Loading...
15 |
16 |
17 |
18 |
19 |
20 |
Progress Records
21 |
22 | {{ errorMessage }}
23 |
24 |
28 | No progress records available.
29 |
30 |
31 |
32 |
33 |
37 |
38 |
39 |
40 |
41 | User: {{ progress.student }}
42 |
43 |
44 | Lesson: {{ progress.lesson }}
45 |
46 |
47 | Status:
48 | {{ progress.completed ? "Completed" : "Not Completed" }}
49 |
50 |
51 |
52 | Completed At: {{ progress.completed_at }}
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/components/progress-list/progress-list.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { ProgressService } from '../../services/progress.service';
3 | import { HttpClient, HttpHeaders } from '@angular/common/http';
4 | import { CommonModule } from '@angular/common';
5 | import { forkJoin } from 'rxjs';
6 | import { Chart, ChartConfiguration, registerables } from 'chart.js';
7 |
8 | interface Progress {
9 | _id: string;
10 | student: string;
11 | lesson: string;
12 | completed: boolean;
13 | completed_at: string | null;
14 | }
15 |
16 | interface MappedProgress {
17 | _id: string;
18 | student: string;
19 | lesson: string;
20 | completed: boolean;
21 | completed_at: string;
22 | }
23 |
24 | Chart.register(...registerables);
25 |
26 | @Component({
27 | selector: 'app-progress-list',
28 | standalone: true,
29 | imports: [CommonModule],
30 | templateUrl: './progress-list.component.html',
31 | styleUrls: ['./progress-list.component.css'],
32 | })
33 | export class ProgressListComponent implements OnInit {
34 | progressRecords: MappedProgress[] = []; // This will now hold the new list of mapped progress data
35 | errorMessage: string = '';
36 | loading: boolean = true; // Track loading state
37 | chart: Chart<'pie'> | undefined;
38 | private apiUrl =
39 | 'https://learning-management-system-fullstack.onrender.com/api/';
40 | lessonsLength: number = 0;
41 | usersLength: number = 0;
42 |
43 | constructor(
44 | private progressService: ProgressService,
45 | private http: HttpClient,
46 | ) {}
47 |
48 | ngOnInit(): void {
49 | this.fetchAllData();
50 | }
51 |
52 | fetchAllData(): void {
53 | const token = localStorage.getItem('authToken');
54 | const headers = new HttpHeaders().set('Authorization', `Token ${token}`);
55 |
56 | const progress$ = this.progressService.getProgress();
57 | const lessons$ = this.http.get(`${this.apiUrl}lessons/`, {
58 | headers,
59 | }); // Fetch lessons
60 | const users$ = this.http.get(`${this.apiUrl}users/`, { headers }); // Fetch users
61 |
62 | forkJoin([progress$, lessons$, users$]).subscribe(
63 | ([progressData, lessonsData, usersData]: [Progress[], any[], any[]]) => {
64 | this.lessonsLength = lessonsData.length;
65 | this.usersLength = usersData.length;
66 |
67 | this.progressRecords = this.mapUserAndLessonDetails(
68 | progressData,
69 | usersData,
70 | lessonsData,
71 | ); // Create a new list
72 | this.loading = false;
73 | this.renderChart();
74 | },
75 | (error) => {
76 | this.loading = false;
77 | this.errorMessage = 'Unauthorized access. Please log in.';
78 | },
79 | );
80 | }
81 |
82 | // Helper function to get a random item from an array
83 | getRandomItem(arr: any[]): any {
84 | const randomIndex = Math.floor(Math.random() * arr.length);
85 | return arr[randomIndex];
86 | }
87 |
88 | // Create a new list of progresses with random student names and lesson titles
89 | mapUserAndLessonDetails(
90 | progressData: Progress[],
91 | usersData: any[],
92 | lessonsData: any[],
93 | ): MappedProgress[] {
94 | return progressData.map((progress) => {
95 | const randomUser = this.getRandomItem(usersData);
96 | const randomLesson = this.getRandomItem(lessonsData);
97 |
98 | return {
99 | _id: progress._id,
100 | student: randomUser ? randomUser.username : 'Unknown User', // Assign random user
101 | lesson: randomLesson ? randomLesson.title : 'Unknown Lesson', // Assign random lesson
102 | completed: progress.completed,
103 | completed_at: progress.completed_at
104 | ? new Date(progress.completed_at).toLocaleDateString()
105 | : 'N/A', // Format date or show 'N/A'
106 | };
107 | });
108 | }
109 |
110 | renderChart(): void {
111 | const ctx = document.getElementById('progressChart') as HTMLCanvasElement;
112 |
113 | if (this.chart) {
114 | this.chart.destroy(); // Destroy any existing chart instance before creating a new one
115 | }
116 |
117 | const chartConfig: ChartConfiguration<'pie'> = {
118 | type: 'pie',
119 | data: {
120 | labels: ['Completed', 'Not Completed'],
121 | datasets: [
122 | {
123 | data: [
124 | this.progressRecords.filter((record) => record.completed).length,
125 | this.progressRecords.filter((record) => !record.completed).length,
126 | ],
127 | backgroundColor: ['#28a745', '#dc3545'],
128 | },
129 | ],
130 | },
131 | options: {
132 | responsive: true,
133 | plugins: {
134 | legend: {
135 | position: 'bottom',
136 | },
137 | },
138 | },
139 | };
140 |
141 | this.chart = new Chart(ctx, chartConfig);
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/components/user-list/user-list.component.css:
--------------------------------------------------------------------------------
1 | .container {
2 | max-width: 1200px;
3 | margin: auto;
4 | }
5 |
6 | .chart-section {
7 | margin-bottom: 20px;
8 | display: flex;
9 | justify-content: center;
10 | margin-top: 20px;
11 | }
12 |
13 | .chart-container {
14 | width: 300px;
15 | height: 300px;
16 | }
17 |
18 | .card {
19 | border: none;
20 | transition:
21 | transform 0.3s,
22 | box-shadow 0.3s;
23 | padding: 15px;
24 | }
25 |
26 | .card:hover {
27 | transform: translateY(-5px);
28 | box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
29 | }
30 |
31 | .card-title {
32 | font-size: 1.5rem;
33 | font-weight: 700;
34 | margin-bottom: 10px;
35 | }
36 |
37 | .card-text {
38 | color: #666;
39 | font-size: 0.95rem;
40 | }
41 |
42 | .rounded-custom {
43 | border-radius: 15px;
44 | margin: 15px;
45 | }
46 |
47 | .loading-overlay {
48 | position: fixed; /* Cover the entire viewport */
49 | top: 0;
50 | left: 0;
51 | width: 100vw;
52 | height: 100vh;
53 | display: flex;
54 | justify-content: center;
55 | align-items: center;
56 | background-color: rgba(
57 | 255,
58 | 255,
59 | 255,
60 | 0.8
61 | ); /* Optional: light overlay effect */
62 | z-index: 9999; /* Ensure it stays on top */
63 | }
64 |
65 | .spinner-border {
66 | width: 3rem;
67 | height: 3rem;
68 | }
69 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/components/user-list/user-list.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Platform Overview
3 |
4 |
5 |
6 |
7 | Loading...
8 |
9 |
10 |
11 |
12 |
17 |
18 |
User List
19 |
20 | {{ errorMessage }}
21 |
22 |
26 | No users available.
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
{{ user.username }}
36 |
{{ user.email }}
37 |
38 | Bio: {{ user.bio }}
39 |
40 |
41 |
42 | Status:
43 | {{ user.is_instructor ? "Instructor" : "Student" }}
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/components/user-list/user-list.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { UserService } from '../../services/user.service';
3 | import { CommonModule } from '@angular/common';
4 | import { Chart, ChartConfiguration, registerables } from 'chart.js';
5 |
6 | Chart.register(...registerables);
7 |
8 | @Component({
9 | selector: 'app-user-list',
10 | standalone: true,
11 | imports: [CommonModule],
12 | templateUrl: './user-list.component.html',
13 | styleUrls: ['./user-list.component.css'],
14 | })
15 | export class UserListComponent implements OnInit {
16 | users: any[] = [];
17 | errorMessage: string = '';
18 | loading: boolean = true; // Added to track the loading state
19 | chart: Chart<'pie'> | undefined;
20 |
21 | constructor(private userService: UserService) {}
22 |
23 | ngOnInit(): void {
24 | this.userService.getUsers().subscribe(
25 | (data) => {
26 | this.users = data;
27 | this.loading = false; // Set loading to false once data is fetched
28 | this.renderChart();
29 | },
30 | (error) => {
31 | this.loading = false; // Stop loading even if there is an error
32 | if (error.status === 401) {
33 | this.errorMessage = 'Unauthorized access. Please log in.';
34 | } else {
35 | this.errorMessage =
36 | 'Error fetching data due to expired token. Please try registering and logging in again.';
37 | }
38 | },
39 | );
40 | }
41 |
42 | renderChart(): void {
43 | const ctx = document.getElementById('userChart') as HTMLCanvasElement;
44 |
45 | if (this.chart) {
46 | this.chart.destroy();
47 | }
48 |
49 | const chartConfig: ChartConfiguration<'pie'> = {
50 | type: 'pie',
51 | data: {
52 | labels: ['Students', 'Instructors'],
53 | datasets: [
54 | {
55 | data: [
56 | this.users.filter((user) => !user.is_instructor).length,
57 | this.users.filter((user) => user.is_instructor).length,
58 | ],
59 | backgroundColor: ['#007bff', '#ffc107'],
60 | },
61 | ],
62 | },
63 | options: {
64 | responsive: true,
65 | plugins: {
66 | legend: {
67 | position: 'bottom',
68 | },
69 | },
70 | },
71 | };
72 |
73 | this.chart = new Chart(ctx, chartConfig);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/core/footer/footer.component.css:
--------------------------------------------------------------------------------
1 | .footer-custom {
2 | background-color: #007bff;
3 | padding: 0;
4 | color: white;
5 | width: 100%;
6 | display: flex;
7 | flex-direction: column;
8 | align-items: center;
9 | margin-top: auto;
10 | font-size: 16px;
11 | font-weight: 500;
12 | }
13 |
14 | .footer-links {
15 | list-style-type: none;
16 | padding: 0;
17 | margin: 0;
18 | display: flex;
19 | justify-content: center;
20 | }
21 |
22 | .footer-links li {
23 | margin-right: 15px;
24 | }
25 |
26 | .nav-link {
27 | color: white !important;
28 | text-decoration: none;
29 | margin: 5px 15px;
30 | font-size: 16px;
31 | font-weight: 500;
32 | transition:
33 | color 0.3s,
34 | transform 0.3s;
35 | position: relative;
36 | }
37 |
38 | .nav-link::after {
39 | content: "";
40 | display: block;
41 | width: 0;
42 | height: 2px;
43 | background: #ffffff;
44 | transition: width 0.3s;
45 | position: absolute;
46 | bottom: -5px;
47 | left: 0;
48 | }
49 |
50 | .nav-link:hover::after {
51 | width: 100%;
52 | }
53 |
54 | .nav-link:hover {
55 | color: #dfe6e9 !important;
56 | transform: scale(1.1);
57 | cursor: pointer;
58 | }
59 |
60 | .active {
61 | font-weight: bold;
62 | color: #ffd700 !important;
63 | }
64 |
65 | /* Styling for social media icons */
66 | .social-icons {
67 | display: flex;
68 | justify-content: center;
69 | gap: 20px; /* Adds space between the icons */
70 | }
71 |
72 | .icon-link {
73 | color: #ffffff;
74 | transition:
75 | transform 0.3s ease,
76 | color 0.3s ease; /* Smooth transition on hover */
77 | }
78 |
79 | .icon-link:hover {
80 | color: #fff; /* Change icon color on hover */
81 | transform: translateY(-5px); /* Slight bounce effect on hover */
82 | }
83 |
84 | .icon-link:hover .fa-github {
85 | color: #fff;
86 | }
87 |
88 | .icon-link:hover .fa-linkedin {
89 | color: #fff;
90 | }
91 |
92 | .icon-link:hover .fa-globe {
93 | color: #fff;
94 | }
95 |
96 | .icon-link:hover .fa-envelope {
97 | color: #fff;
98 | }
99 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/core/footer/footer.component.html:
--------------------------------------------------------------------------------
1 |
94 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/core/footer/footer.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnDestroy, OnInit } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { Router, RouterModule } from '@angular/router';
4 | import { Subscription } from 'rxjs';
5 | import { AuthService } from '../../services/auth.service';
6 |
7 | @Component({
8 | selector: 'app-footer',
9 | standalone: true,
10 | imports: [CommonModule, RouterModule],
11 | templateUrl: './footer.component.html',
12 | styleUrls: ['./footer.component.css'],
13 | })
14 | export class FooterComponent implements OnInit, OnDestroy {
15 | private authSubscription: Subscription = new Subscription();
16 | private loggedInStatus: boolean = false;
17 | currentYear: number = new Date().getFullYear();
18 |
19 | constructor(
20 | private authService: AuthService,
21 | private router: Router,
22 | ) {}
23 |
24 | ngOnInit(): void {
25 | this.authSubscription = this.authService
26 | .isLoggedIn()
27 | .subscribe((status) => {
28 | this.loggedInStatus = status;
29 | });
30 | }
31 |
32 | isLoggedIn(): boolean {
33 | return this.loggedInStatus;
34 | }
35 |
36 | logout(): void {
37 | this.authService.logout();
38 | this.router.navigate(['/login']);
39 | }
40 |
41 | ngOnDestroy(): void {
42 | if (this.authSubscription) {
43 | this.authSubscription.unsubscribe();
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/core/header/header.component.css:
--------------------------------------------------------------------------------
1 | /* Custom Navbar Styling */
2 | .navbar-custom {
3 | background-color: #007bff;
4 | color: white;
5 | padding: 10px;
6 | width: 100%;
7 | }
8 |
9 | .navbar-brand {
10 | font-size: 24px;
11 | font-weight: bold;
12 | color: white;
13 | text-align: center;
14 | white-space: normal; /* Allow text to wrap */
15 | max-width: 100%; /* Ensure it wraps within its container */
16 | margin-right: 0;
17 | }
18 |
19 | .navbar-links {
20 | list-style-type: none;
21 | padding: 0;
22 | margin: 0;
23 | }
24 |
25 | .navbar-links li {
26 | margin-right: 20px;
27 | }
28 |
29 | .nav-link {
30 | color: white !important;
31 | text-decoration: none;
32 | transition:
33 | color 0.3s,
34 | transform 0.3s;
35 | font-size: 16px;
36 | font-weight: 500;
37 | position: relative;
38 | }
39 |
40 | .nav-link::after {
41 | content: "";
42 | display: block;
43 | width: 0;
44 | height: 2px;
45 | background: #ffffff;
46 | transition: width 0.3s;
47 | position: absolute;
48 | bottom: -5px;
49 | left: 0;
50 | }
51 |
52 | .nav-link:hover::after {
53 | width: 100%;
54 | }
55 |
56 | .nav-link:hover {
57 | color: #dfe6e9 !important;
58 | transform: scale(1.05);
59 | }
60 |
61 | .active {
62 | font-weight: bold;
63 | color: #ffd700 !important;
64 | }
65 |
66 | /*!* Responsive Design Adjustments *!*/
67 | /*@media (max-width: 991.98px) {*/
68 | /* .container-fluid {*/
69 | /* display: flex;*/
70 | /* flex-direction: column;*/
71 | /* align-items: center; !* Center align the elements *!*/
72 | /* }*/
73 |
74 | /* .navbar-brand {*/
75 | /* text-align: center;*/
76 | /* font-size: 24px;*/
77 | /* white-space: normal; !* Allow text to wrap *!*/
78 | /* }*/
79 |
80 | /* .navbar-links {*/
81 | /* flex-direction: column;*/
82 | /* align-items: center;*/
83 | /* }*/
84 |
85 | /* .navbar-links li {*/
86 | /* margin-right: 0;*/
87 | /* margin-bottom: 10px;*/
88 | /* }*/
89 |
90 | /* .navbar-toggler {*/
91 | /* margin-top: 10px;*/
92 | /* }*/
93 | /*}*/
94 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/core/header/header.component.html:
--------------------------------------------------------------------------------
1 |
2 |
106 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/core/header/header.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, OnDestroy } from '@angular/core';
2 | import { AuthService } from '../../services/auth.service';
3 | import { Router, RouterModule } from '@angular/router';
4 | import { CommonModule } from '@angular/common';
5 | import { Subscription, interval } from 'rxjs';
6 | import { switchMap, startWith } from 'rxjs/operators';
7 |
8 | @Component({
9 | selector: 'app-header',
10 | standalone: true,
11 | imports: [RouterModule, CommonModule],
12 | templateUrl: './header.component.html',
13 | styleUrls: ['./header.component.css'],
14 | })
15 | export class HeaderComponent implements OnInit, OnDestroy {
16 | private authSubscription: Subscription = new Subscription();
17 | private loggedInStatus: boolean = false;
18 |
19 | constructor(
20 | private authService: AuthService,
21 | private router: Router,
22 | ) {}
23 |
24 | ngOnInit(): void {
25 | // Start with an immediate check, then every 2 seconds
26 | this.authSubscription = interval(2000)
27 | .pipe(
28 | startWith(0), // Immediately triggers the first emission
29 | switchMap(() => this.authService.isTokenValid()), // Check token validity
30 | )
31 | .subscribe((isValid) => {
32 | this.loggedInStatus = isValid;
33 | });
34 | }
35 |
36 | isLoggedIn(): boolean {
37 | return this.loggedInStatus;
38 | }
39 |
40 | logout(): void {
41 | this.authService.logout();
42 | this.loggedInStatus = false; // Update immediately on logout
43 | }
44 |
45 | ngOnDestroy(): void {
46 | if (this.authSubscription) {
47 | this.authSubscription.unsubscribe();
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/pages/home/home.component.css:
--------------------------------------------------------------------------------
1 | .home {
2 | max-width: 900px;
3 | margin: auto;
4 | padding: 20px;
5 | }
6 |
7 | .welcome-section {
8 | margin-bottom: 30px;
9 | }
10 |
11 | .welcome-section h1 {
12 | font-size: 2.5rem;
13 | font-weight: 700;
14 | }
15 |
16 | .welcome-section p {
17 | font-size: 1.25rem;
18 | color: #555;
19 | }
20 |
21 | .card {
22 | border: none;
23 | transition:
24 | transform 0.3s,
25 | box-shadow 0.3s;
26 | margin: 10px;
27 | padding: 10px;
28 | }
29 |
30 | .card:hover {
31 | transform: translateY(-5px);
32 | box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
33 | }
34 |
35 | .lead {
36 | text-align: center;
37 | }
38 |
39 | .card-title {
40 | font-size: 1.2rem;
41 | font-weight: 600;
42 | margin-top: 10px;
43 | }
44 |
45 | .card-text {
46 | color: #777;
47 | text-align: center;
48 | }
49 |
50 | .btn-outline-primary {
51 | font-weight: 500;
52 | transition:
53 | color 0.3s,
54 | border-color 0.3s,
55 | background-color 0.3s;
56 | padding: 5px;
57 | border-radius: 8px;
58 | }
59 |
60 | .btn-outline-primary:hover {
61 | background-color: #007bff;
62 | color: white;
63 | border-color: #007bff;
64 | }
65 |
66 | .rounded-custom {
67 | border-radius: 15px;
68 | }
69 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/pages/home/home.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Welcome to the E-Learning Management System
6 |
7 |
8 | Explore courses, track your progress, and manage enrollments easily.
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
20 |
21 |
22 |
User List
23 |
Manage all registered users.
24 |
25 |
33 |
34 |
35 |
36 |
37 |
38 |
41 |
42 |
43 |
Course List
44 |
Browse all available courses.
45 |
46 |
54 |
55 |
56 |
57 |
58 |
59 |
62 |
63 |
64 |
Lesson List
65 |
View all lessons in courses.
66 |
67 |
75 |
76 |
77 |
78 |
79 |
80 |
83 |
84 |
85 |
Enrollment List
86 |
Manage course enrollments.
87 |
88 |
96 |
97 |
98 |
99 |
100 |
101 |
104 |
105 |
106 |
Progress Records
107 |
Track user progress in courses.
108 |
109 |
117 |
118 |
119 |
120 |
121 |
122 |
125 |
126 |
127 |
Register
128 |
Register an account to get started as an LMS administrator.
129 |
130 |
138 |
139 |
140 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/pages/home/home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { RouterModule } from '@angular/router';
3 |
4 | @Component({
5 | selector: 'app-home',
6 | standalone: true,
7 | imports: [RouterModule],
8 | templateUrl: './home.component.html',
9 | styleUrls: ['./home.component.css'],
10 | })
11 | export class HomeComponent {}
12 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/pages/notfound/notfound.component.css:
--------------------------------------------------------------------------------
1 | .not-found {
2 | margin-top: 5rem;
3 | }
4 |
5 | .not-found h1 {
6 | font-size: 6rem;
7 | font-weight: bold;
8 | }
9 |
10 | .not-found h2 {
11 | font-size: 2.5rem;
12 | font-weight: 600;
13 | }
14 |
15 | .not-found p {
16 | font-size: 1.25rem;
17 | }
18 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/pages/notfound/notfound.component.html:
--------------------------------------------------------------------------------
1 |
2 |
404
3 |
Page Not Found
4 |
Sorry, the page you are looking for does not exist.
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/pages/notfound/notfound.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { Router } from '@angular/router';
3 |
4 | @Component({
5 | selector: 'app-not-found',
6 | templateUrl: './notfound.component.html',
7 | standalone: true,
8 | styleUrls: ['./notfound.component.css'],
9 | })
10 | export class NotFoundComponent {
11 | constructor(private router: Router) {}
12 |
13 | // Method to navigate back to the home page
14 | goToHome(): void {
15 | this.router.navigate(['/']);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/services/auth.interceptor.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import {
3 | HttpInterceptor,
4 | HttpRequest,
5 | HttpHandler,
6 | HttpEvent,
7 | } from '@angular/common/http';
8 | import { Observable } from 'rxjs';
9 | import { AuthService } from './auth.service';
10 |
11 | @Injectable()
12 | export class AuthInterceptor implements HttpInterceptor {
13 | constructor(private authService: AuthService) {}
14 |
15 | intercept(
16 | req: HttpRequest,
17 | next: HttpHandler,
18 | ): Observable> {
19 | const token = this.authService.getToken();
20 |
21 | if (token) {
22 | const cloned = req.clone({
23 | setHeaders: {
24 | Authorization: `Token ${token}`,
25 | },
26 | });
27 | console.log('AuthInterceptor: Attaching token to request:', cloned);
28 | return next.handle(cloned);
29 | } else {
30 | return next.handle(req);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/services/auth.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient, HttpHeaders } from '@angular/common/http';
3 | import { Observable, BehaviorSubject, of } from 'rxjs';
4 | import { catchError, map, tap } from 'rxjs/operators';
5 |
6 | @Injectable({
7 | providedIn: 'root',
8 | })
9 | export class AuthService {
10 | private loginUrl =
11 | 'https://learning-management-system-fullstack.onrender.com/api/auth/login/';
12 | private registerUrl =
13 | 'https://learning-management-system-fullstack.onrender.com/api/auth/registration/'; // Registration endpoint
14 | private validateUrl =
15 | 'https://learning-management-system-fullstack.onrender.com/api/enrollments/'; // Token validation endpoint
16 | private tokenKey = 'authToken';
17 | private isLoggedInSubject = new BehaviorSubject(this.hasToken());
18 |
19 | constructor(private http: HttpClient) {}
20 |
21 | // Login method
22 | login(username: string, password: string): Observable {
23 | return this.http.post(this.loginUrl, { username, password }).pipe(
24 | tap((response: any) => {
25 | if (response && response.key) {
26 | localStorage.setItem(this.tokenKey, response.key);
27 | this.isLoggedInSubject.next(true);
28 | }
29 | }),
30 | );
31 | }
32 |
33 | // Registration method
34 | register(
35 | username: string,
36 | email: string,
37 | password1: string,
38 | password2: string,
39 | ): Observable {
40 | return this.http
41 | .post(this.registerUrl, {
42 | username,
43 | email,
44 | password1,
45 | password2,
46 | })
47 | .pipe(
48 | tap((response: any) => {
49 | console.log('Registration successful:', response);
50 | }),
51 | );
52 | }
53 |
54 | // Logout method
55 | logout(): void {
56 | localStorage.removeItem(this.tokenKey);
57 | this.isLoggedInSubject.next(false);
58 | }
59 |
60 | // Get the stored token
61 | getToken(): string | null {
62 | return localStorage.getItem(this.tokenKey);
63 | }
64 |
65 | // Check if the user is logged in
66 | isLoggedIn(): Observable {
67 | return this.isLoggedInSubject.asObservable();
68 | }
69 |
70 | // Check if the token is valid by making a test API request
71 | isTokenValid(): Observable {
72 | const token = this.getToken();
73 | if (!token) {
74 | this.isLoggedInSubject.next(false);
75 | return of(false); // No token means invalid
76 | }
77 |
78 | // Set up headers with the token
79 | const headers = new HttpHeaders({
80 | Authorization: `Token ${token}`,
81 | 'Content-Type': 'application/json',
82 | });
83 |
84 | // Make a request to validate the token
85 | return this.http.get(this.validateUrl, { headers }).pipe(
86 | map(() => {
87 | this.isLoggedInSubject.next(true); // Token is valid
88 | return true;
89 | }),
90 | catchError((error) => {
91 | if (error.status === 401) {
92 | this.logout(); // Clear the token if it's invalid
93 | return of(false);
94 | }
95 | return of(true); // Other errors treated as valid for now
96 | }),
97 | );
98 | }
99 |
100 | // Private method to check if a token is stored
101 | private hasToken(): boolean {
102 | return !!this.getToken();
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/services/course.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient, HttpHeaders } from '@angular/common/http';
3 | import { Observable } from 'rxjs';
4 | import { AuthService } from './auth.service';
5 |
6 | @Injectable({
7 | providedIn: 'root',
8 | })
9 | export class CourseService {
10 | private apiUrl =
11 | 'https://learning-management-system-fullstack.onrender.com/api/courses/';
12 |
13 | constructor(
14 | private http: HttpClient,
15 | private authService: AuthService,
16 | ) {}
17 |
18 | // Helper method to get the authorization headers
19 | private getAuthHeaders(): HttpHeaders {
20 | const token = this.authService.getToken();
21 | return new HttpHeaders({
22 | Authorization: `Token ${token}`,
23 | 'Content-Type': 'application/json',
24 | });
25 | }
26 |
27 | // Get all courses
28 | getCourses(): Observable {
29 | const headers = this.getAuthHeaders();
30 | return this.http.get(this.apiUrl, { headers });
31 | }
32 |
33 | // Get a specific course by ID
34 | getCourse(id: number): Observable {
35 | const headers = this.getAuthHeaders();
36 | return this.http.get(`${this.apiUrl}${id}/`, { headers });
37 | }
38 |
39 | // Create a new course
40 | createCourse(course: any): Observable {
41 | const headers = this.getAuthHeaders();
42 | return this.http.post(this.apiUrl, course, { headers });
43 | }
44 |
45 | // Update an existing course by ID
46 | updateCourse(id: number, course: any): Observable {
47 | const headers = this.getAuthHeaders();
48 | return this.http.put(`${this.apiUrl}${id}/`, course, { headers });
49 | }
50 |
51 | // Delete a course by ID
52 | deleteCourse(id: number): Observable {
53 | const headers = this.getAuthHeaders();
54 | return this.http.delete(`${this.apiUrl}${id}/`, { headers });
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/services/enrollment.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient, HttpHeaders } from '@angular/common/http';
3 | import { Observable } from 'rxjs';
4 | import { AuthService } from './auth.service';
5 |
6 | @Injectable({
7 | providedIn: 'root',
8 | })
9 | export class EnrollmentService {
10 | private apiUrl =
11 | 'https://learning-management-system-fullstack.onrender.com/api/enrollments/';
12 |
13 | constructor(
14 | private http: HttpClient,
15 | private authService: AuthService,
16 | ) {}
17 |
18 | // Helper method to get the authorization headers
19 | private getAuthHeaders(): HttpHeaders {
20 | const token = this.authService.getToken();
21 | return new HttpHeaders({
22 | Authorization: `Token ${token}`,
23 | 'Content-Type': 'application/json',
24 | });
25 | }
26 |
27 | // Get all enrollments
28 | getEnrollments(): Observable {
29 | const headers = this.getAuthHeaders();
30 | return this.http.get(this.apiUrl, { headers });
31 | }
32 |
33 | // Get a specific enrollment by ID
34 | getEnrollment(id: number): Observable {
35 | const headers = this.getAuthHeaders();
36 | return this.http.get(`${this.apiUrl}${id}/`, { headers });
37 | }
38 |
39 | // Create a new enrollment
40 | createEnrollment(enrollment: any): Observable {
41 | const headers = this.getAuthHeaders();
42 | return this.http.post(this.apiUrl, enrollment, { headers });
43 | }
44 |
45 | // Update an existing enrollment by ID
46 | updateEnrollment(id: number, enrollment: any): Observable {
47 | const headers = this.getAuthHeaders();
48 | return this.http.put(`${this.apiUrl}${id}/`, enrollment, { headers });
49 | }
50 |
51 | // Delete an enrollment by ID
52 | deleteEnrollment(id: number): Observable {
53 | const headers = this.getAuthHeaders();
54 | return this.http.delete(`${this.apiUrl}${id}/`, { headers });
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/services/lesson.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient, HttpHeaders } from '@angular/common/http';
3 | import { Observable } from 'rxjs';
4 | import { AuthService } from './auth.service';
5 |
6 | @Injectable({
7 | providedIn: 'root',
8 | })
9 | export class LessonService {
10 | private apiUrl =
11 | 'https://learning-management-system-fullstack.onrender.com/api/lessons/';
12 |
13 | constructor(
14 | private http: HttpClient,
15 | private authService: AuthService,
16 | ) {}
17 |
18 | // Helper method to get the authorization headers
19 | private getAuthHeaders(): HttpHeaders {
20 | const token = this.authService.getToken();
21 | return new HttpHeaders({
22 | Authorization: `Token ${token}`,
23 | 'Content-Type': 'application/json',
24 | });
25 | }
26 |
27 | // Get all lessons
28 | getLessons(): Observable {
29 | const headers = this.getAuthHeaders();
30 | return this.http.get(this.apiUrl, { headers });
31 | }
32 |
33 | // Get a specific lesson by ID
34 | getLesson(id: number): Observable {
35 | const headers = this.getAuthHeaders();
36 | return this.http.get(`${this.apiUrl}${id}/`, { headers });
37 | }
38 |
39 | // Create a new lesson
40 | createLesson(lesson: any): Observable {
41 | const headers = this.getAuthHeaders();
42 | return this.http.post(this.apiUrl, lesson, { headers });
43 | }
44 |
45 | // Update an existing lesson by ID
46 | updateLesson(id: number, lesson: any): Observable {
47 | const headers = this.getAuthHeaders();
48 | return this.http.put(`${this.apiUrl}${id}/`, lesson, { headers });
49 | }
50 |
51 | // Delete a lesson by ID
52 | deleteLesson(id: number): Observable {
53 | const headers = this.getAuthHeaders();
54 | return this.http.delete(`${this.apiUrl}${id}/`, { headers });
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/services/progress.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient, HttpHeaders } from '@angular/common/http';
3 | import { Observable } from 'rxjs';
4 | import { AuthService } from './auth.service';
5 |
6 | @Injectable({
7 | providedIn: 'root',
8 | })
9 | export class ProgressService {
10 | private apiUrl =
11 | 'https://learning-management-system-fullstack.onrender.com/api/progress/';
12 |
13 | constructor(
14 | private http: HttpClient,
15 | private authService: AuthService,
16 | ) {}
17 |
18 | // Helper method to get the authorization headers
19 | private getAuthHeaders(): HttpHeaders {
20 | const token = this.authService.getToken();
21 | return new HttpHeaders({
22 | Authorization: `Token ${token}`,
23 | 'Content-Type': 'application/json',
24 | });
25 | }
26 |
27 | // Get all progress records
28 | getProgress(): Observable {
29 | const headers = this.getAuthHeaders();
30 | return this.http.get(this.apiUrl, { headers });
31 | }
32 |
33 | // Get a specific progress record by ID
34 | getProgressRecord(id: number): Observable {
35 | const headers = this.getAuthHeaders();
36 | return this.http.get(`${this.apiUrl}${id}/`, { headers });
37 | }
38 |
39 | // Create a new progress record
40 | createProgress(progress: any): Observable {
41 | const headers = this.getAuthHeaders();
42 | return this.http.post(this.apiUrl, progress, { headers });
43 | }
44 |
45 | // Update an existing progress record by ID
46 | updateProgress(id: number, progress: any): Observable {
47 | const headers = this.getAuthHeaders();
48 | return this.http.put(`${this.apiUrl}${id}/`, progress, { headers });
49 | }
50 |
51 | // Delete a progress record by ID
52 | deleteProgress(id: number): Observable {
53 | const headers = this.getAuthHeaders();
54 | return this.http.delete(`${this.apiUrl}${id}/`, { headers });
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/app/services/user.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient, HttpHeaders } from '@angular/common/http';
3 | import { Observable } from 'rxjs';
4 | import { AuthService } from './auth.service';
5 |
6 | @Injectable({
7 | providedIn: 'root',
8 | })
9 | export class UserService {
10 | private apiUrl =
11 | 'https://learning-management-system-fullstack.onrender.com/api/users/';
12 |
13 | constructor(
14 | private http: HttpClient,
15 | private authService: AuthService,
16 | ) {}
17 |
18 | private getAuthHeaders(): HttpHeaders {
19 | const token = this.authService.getToken();
20 | return new HttpHeaders({
21 | Authorization: `Token ${token}`,
22 | 'Content-Type': 'application/json',
23 | });
24 | }
25 |
26 | getUsers(): Observable {
27 | const headers = this.getAuthHeaders();
28 | return this.http.get(this.apiUrl, { headers });
29 | }
30 |
31 | getUser(id: number): Observable {
32 | const headers = this.getAuthHeaders();
33 | return this.http.get(`${this.apiUrl}${id}/`, { headers });
34 | }
35 |
36 | createUser(user: any): Observable {
37 | const headers = this.getAuthHeaders();
38 | return this.http.post(this.apiUrl, user, { headers });
39 | }
40 |
41 | updateUser(id: number, user: any): Observable {
42 | const headers = this.getAuthHeaders();
43 | return this.http.put(`${this.apiUrl}${id}/`, user, { headers });
44 | }
45 |
46 | deleteUser(id: number): Observable {
47 | const headers = this.getAuthHeaders();
48 | return this.http.delete(`${this.apiUrl}${id}/`, { headers });
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Frontend/app/src/apple-touch-icon.png
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Frontend/app/src/assets/.gitkeep
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/assets/icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Frontend/app/src/assets/icon-192x192.png
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/assets/icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Frontend/app/src/assets/icon-512x512.png
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Frontend/app/src/favicon-16x16.png
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Frontend/app/src/favicon-32x32.png
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/LMS-Frontend/app/src/favicon.ico
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Learning Management System - A Platform to Learn and Teach
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/main.ts:
--------------------------------------------------------------------------------
1 | import { bootstrapApplication } from '@angular/platform-browser';
2 | import { AppComponent } from './app/app.component';
3 | import {
4 | provideHttpClient,
5 | HTTP_INTERCEPTORS,
6 | withFetch,
7 | } from '@angular/common/http';
8 | import { provideRouter } from '@angular/router';
9 | import { routes } from './app/app.routes';
10 | import { AuthInterceptor } from './app/services/auth.interceptor';
11 |
12 | bootstrapApplication(AppComponent, {
13 | providers: [
14 | provideRouter(routes),
15 | provideHttpClient(withFetch()),
16 | { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
17 | ],
18 | }).catch((err) => console.error(err));
19 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "E-Learning Management System",
3 | "short_name": "LMS",
4 | "description": "A Progressive Web App for E-Learning Management System",
5 | "display": "standalone",
6 | "start_url": "/",
7 | "scope": "/",
8 | "background_color": "#ffffff",
9 | "theme_color": "#007bff",
10 | "icons": [
11 | {
12 | "src": "assets/icon-192x192.png",
13 | "sizes": "192x192",
14 | "type": "image/png"
15 | },
16 | {
17 | "src": "assets/icon-512x512.png",
18 | "sizes": "512x512",
19 | "type": "image/png"
20 | }
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/src/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: "Poppins", sans-serif;
3 | background-color: #f4f4f9;
4 | color: #333;
5 | margin: 0;
6 | padding: 0;
7 | overflow-x: hidden;
8 | }
9 |
10 | h1,
11 | h2,
12 | h3,
13 | h4,
14 | h5,
15 | h6 {
16 | font-weight: 600;
17 | }
18 |
19 | p,
20 | li,
21 | a {
22 | font-weight: 400;
23 | line-height: 1.6;
24 | }
25 |
26 | h1,
27 | h2,
28 | h3,
29 | h4,
30 | h5,
31 | h6 {
32 | margin: 0;
33 | padding-bottom: 10px;
34 | border-bottom: 2px solid #ddd;
35 | color: #444;
36 | }
37 |
38 | .container {
39 | padding: 20px;
40 | margin-top: 20px;
41 | }
42 |
43 | button {
44 | background-color: #007bff;
45 | border: none;
46 | color: white;
47 | padding: 10px 20px;
48 | text-align: center;
49 | text-decoration: none;
50 | display: inline-block;
51 | font-size: 16px;
52 | margin: 4px 2px;
53 | cursor: pointer;
54 | border-radius: 5px;
55 | }
56 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/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 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/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": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": true,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "ES2022",
20 | "module": "ES2022",
21 | "useDefineForClassFields": false,
22 | "lib": [
23 | "ES2022",
24 | "dom"
25 | ]
26 | },
27 | "angularCompilerOptions": {
28 | "enableI18nLegacyMessageIdFormat": false,
29 | "strictInjectionParameters": true,
30 | "strictInputAccessModifiers": true,
31 | "strictTemplates": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LMS-Frontend/app/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 | "include": [
11 | "src/**/*.spec.ts",
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | backend:
3 | build:
4 | context: ./LMS-Backend
5 | dockerfile: Dockerfile
6 | command: python manage.py runserver 0.0.0.0:8000
7 | volumes:
8 | - ./LMS-Backend:/app
9 | ports:
10 | - "8000:8000"
11 | depends_on:
12 | - mongodb
13 | - redis
14 | environment:
15 | - MONGO_HOST=mongodb
16 | - MONGO_PORT=27017
17 | - REDIS_HOST=redis
18 | - REDIS_PORT=6379
19 |
20 | frontend:
21 | build:
22 | context: ./LMS-Frontend
23 | dockerfile: Dockerfile
24 | ports:
25 | - "8080:8080"
26 | volumes:
27 | - ./LMS-Frontend:/app
28 | stdin_open: true
29 | tty: true
30 |
31 | mongodb:
32 | image: mongo:5.0
33 | container_name: mongo
34 | ports:
35 | - "27017:27017"
36 | volumes:
37 | - mongodb_data:/data/db
38 |
39 | redis:
40 | image: redis:6
41 | container_name: redis
42 | ports:
43 | - "6379:6379"
44 |
45 | volumes:
46 | mongodb_data:
47 |
--------------------------------------------------------------------------------
/docs/admin-ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/docs/admin-ui.png
--------------------------------------------------------------------------------
/docs/browsable-api.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/docs/browsable-api.png
--------------------------------------------------------------------------------
/docs/course-ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/docs/course-ui.png
--------------------------------------------------------------------------------
/docs/enrollments-ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/docs/enrollments-ui.png
--------------------------------------------------------------------------------
/docs/footer-ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/docs/footer-ui.png
--------------------------------------------------------------------------------
/docs/gui-tools.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/docs/gui-tools.png
--------------------------------------------------------------------------------
/docs/home-ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/docs/home-ui.png
--------------------------------------------------------------------------------
/docs/lesson-ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/docs/lesson-ui.png
--------------------------------------------------------------------------------
/docs/login-ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/docs/login-ui.png
--------------------------------------------------------------------------------
/docs/mobile-ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/docs/mobile-ui.png
--------------------------------------------------------------------------------
/docs/notfound.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/docs/notfound.png
--------------------------------------------------------------------------------
/docs/progress-ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/docs/progress-ui.png
--------------------------------------------------------------------------------
/docs/redoc-ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/docs/redoc-ui.png
--------------------------------------------------------------------------------
/docs/register-ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/docs/register-ui.png
--------------------------------------------------------------------------------
/docs/swagger-ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/docs/swagger-ui.png
--------------------------------------------------------------------------------
/docs/unauthorized-ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/docs/unauthorized-ui.png
--------------------------------------------------------------------------------
/docs/user-ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/docs/user-ui.png
--------------------------------------------------------------------------------
/identifier.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hoangsonww/Learning-Management-System-Fullstack/fb61e29f64c690e32e50614b1253165352ae6526/identifier.sqlite
--------------------------------------------------------------------------------
/kubernetes/backend-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: backend-deployment
5 | labels:
6 | app: backend
7 | spec:
8 | replicas: 1
9 | selector:
10 | matchLabels:
11 | app: backend
12 | template:
13 | metadata:
14 | labels:
15 | app: backend
16 | spec:
17 | containers:
18 | - name: backend
19 | image: lms-backend:latest # Build and push this to a container registry
20 | ports:
21 | - containerPort: 3000
22 | env:
23 | - name: NODE_ENV
24 | value: "production"
25 | volumeMounts:
26 | - name: backend-code
27 | mountPath: /app
28 | volumes:
29 | - name: backend-code
30 | hostPath:
31 | path: /home/user/project/backend
32 |
--------------------------------------------------------------------------------
/kubernetes/backend-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: backend-service
5 | spec:
6 | selector:
7 | app: backend
8 | ports:
9 | - protocol: TCP
10 | port: 3000
11 | targetPort: 3000
12 | type: ClusterIP # Internal service
13 |
--------------------------------------------------------------------------------
/kubernetes/configmap.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: lms-app-config
5 | data:
6 | NODE_ENV: "production"
7 | REACT_APP_BACKEND_URL: "http://backend-service:3000"
8 |
--------------------------------------------------------------------------------
/kubernetes/frontend-deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: frontend-deployment
5 | labels:
6 | app: frontend
7 | spec:
8 | replicas: 1
9 | selector:
10 | matchLabels:
11 | app: frontend
12 | template:
13 | metadata:
14 | labels:
15 | app: frontend
16 | spec:
17 | containers:
18 | - name: frontend
19 | image: lms-frontend:latest # Build and push this to a container registry
20 | ports:
21 | - containerPort: 3001
22 | env:
23 | - name: ANGULAR_APP_BACKEND_URL
24 | value: "http://backend-service:3000" # This service will route to the backend service
25 | volumeMounts:
26 | - name: frontend-code
27 | mountPath: /app
28 | volumes:
29 | - name: frontend-code
30 | hostPath:
31 | path: /home/user/project/frontend
32 |
--------------------------------------------------------------------------------
/kubernetes/frontend-service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: frontend-service
5 | spec:
6 | selector:
7 | app: frontend
8 | ports:
9 | - protocol: TCP
10 | port: 3001
11 | targetPort: 3001
12 | type: NodePort # Expose frontend for external access
13 |
--------------------------------------------------------------------------------
/nginx/Dockerfile:
--------------------------------------------------------------------------------
1 | # Base image for nginx
2 | FROM nginx:latest
3 |
4 | # Remove the default nginx config file
5 | RUN rm /etc/nginx/conf.d/default.conf
6 |
7 | # Copy the custom nginx configuration file
8 | COPY nginx.conf /etc/nginx/conf.d/
9 |
10 | # Expose port 80
11 | EXPOSE 80
12 |
13 | # Start Nginx
14 | CMD ["nginx", "-g", "daemon off;"]
15 |
--------------------------------------------------------------------------------
/nginx/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.8'
2 |
3 | services:
4 | nginx:
5 | build:
6 | context: .
7 | dockerfile: Dockerfile
8 | ports:
9 | - "80:80" # Map host port 80 to container port 80
10 | volumes:
11 | - ./nginx.conf:/etc/nginx/conf.d/nginx.conf # Mount custom nginx config file
12 | restart: always # Automatically restart container if it crashes
13 |
--------------------------------------------------------------------------------
/nginx/nginx.conf:
--------------------------------------------------------------------------------
1 | # Load Balancer Configuration
2 | http {
3 | upstream django_backend {
4 | server moodify-emotion-music-app.onrender.com; # Default port: 10000
5 | server moodify-emotion-music-app.onrender.com:8000;
6 | server moodify-emotion-music-app.onrender.com:8001;
7 | server moodify-emotion-music-app.onrender.com:8002;
8 | }
9 |
10 | # Server block for load balancing
11 | server {
12 | listen 80;
13 |
14 | # Define the root for the requests, forward to upstream
15 | location / {
16 | proxy_pass http://django_backend;
17 |
18 | # Set proxy headers
19 | proxy_set_header Host $host;
20 | proxy_set_header X-Real-IP $remote_addr;
21 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
22 | proxy_set_header X-Forwarded-Proto $scheme;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/nginx/start_nginx.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Define the docker-compose file location
4 | COMPOSE_FILE="docker-compose.yml"
5 |
6 | # Display help message
7 | function show_help() {
8 | echo "Usage: $0 [option]"
9 | echo "Options:"
10 | echo " build Build the Nginx Docker image"
11 | echo " start Start the Nginx container"
12 | echo " stop Stop the Nginx container"
13 | echo " restart Restart the Nginx container"
14 | echo " logs Show logs of the Nginx container"
15 | echo " clean Stop and remove the container"
16 | echo " help Display this help message"
17 | }
18 |
19 | # Check if docker-compose file exists
20 | if [ ! -f "$COMPOSE_FILE" ]; then
21 | echo "Error: $COMPOSE_FILE not found!"
22 | exit 1
23 | fi
24 |
25 | # Handle script arguments
26 | case "$1" in
27 | build)
28 | echo "Building the Nginx Docker image..."
29 | docker-compose -f $COMPOSE_FILE build
30 | ;;
31 | start)
32 | echo "Starting the Nginx container..."
33 | docker-compose -f $COMPOSE_FILE up -d
34 | ;;
35 | stop)
36 | echo "Stopping the Nginx container..."
37 | docker-compose -f $COMPOSE_FILE down
38 | ;;
39 | restart)
40 | echo "Restarting the Nginx container..."
41 | docker-compose -f $COMPOSE_FILE down
42 | docker-compose -f $COMPOSE_FILE up -d
43 | ;;
44 | logs)
45 | echo "Displaying logs of the Nginx container..."
46 | docker-compose -f $COMPOSE_FILE logs -f
47 | ;;
48 | clean)
49 | echo "Cleaning up: stopping and removing the container..."
50 | docker-compose -f $COMPOSE_FILE down -v
51 | ;;
52 | help)
53 | show_help
54 | ;;
55 | *)
56 | echo "Invalid option!"
57 | show_help
58 | ;;
59 | esac
60 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "learning-management-system",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "learning-management-system",
9 | "version": "1.0.0",
10 | "hasInstallScript": true,
11 | "license": "MIT",
12 | "dependencies": {
13 | "concurrently": "^9.0.1",
14 | "prettier": "^3.3.3"
15 | }
16 | },
17 | "node_modules/ansi-regex": {
18 | "version": "5.0.1",
19 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
20 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
21 | "engines": {
22 | "node": ">=8"
23 | }
24 | },
25 | "node_modules/ansi-styles": {
26 | "version": "4.3.0",
27 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
28 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
29 | "dependencies": {
30 | "color-convert": "^2.0.1"
31 | },
32 | "engines": {
33 | "node": ">=8"
34 | },
35 | "funding": {
36 | "url": "https://github.com/chalk/ansi-styles?sponsor=1"
37 | }
38 | },
39 | "node_modules/chalk": {
40 | "version": "4.1.2",
41 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
42 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
43 | "dependencies": {
44 | "ansi-styles": "^4.1.0",
45 | "supports-color": "^7.1.0"
46 | },
47 | "engines": {
48 | "node": ">=10"
49 | },
50 | "funding": {
51 | "url": "https://github.com/chalk/chalk?sponsor=1"
52 | }
53 | },
54 | "node_modules/chalk/node_modules/supports-color": {
55 | "version": "7.2.0",
56 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
57 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
58 | "dependencies": {
59 | "has-flag": "^4.0.0"
60 | },
61 | "engines": {
62 | "node": ">=8"
63 | }
64 | },
65 | "node_modules/cliui": {
66 | "version": "8.0.1",
67 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
68 | "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
69 | "dependencies": {
70 | "string-width": "^4.2.0",
71 | "strip-ansi": "^6.0.1",
72 | "wrap-ansi": "^7.0.0"
73 | },
74 | "engines": {
75 | "node": ">=12"
76 | }
77 | },
78 | "node_modules/color-convert": {
79 | "version": "2.0.1",
80 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
81 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
82 | "dependencies": {
83 | "color-name": "~1.1.4"
84 | },
85 | "engines": {
86 | "node": ">=7.0.0"
87 | }
88 | },
89 | "node_modules/color-name": {
90 | "version": "1.1.4",
91 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
92 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
93 | },
94 | "node_modules/concurrently": {
95 | "version": "9.0.1",
96 | "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.0.1.tgz",
97 | "integrity": "sha512-wYKvCd/f54sTXJMSfV6Ln/B8UrfLBKOYa+lzc6CHay3Qek+LorVSBdMVfyewFhRbH0Rbabsk4D+3PL/VjQ5gzg==",
98 | "dependencies": {
99 | "chalk": "^4.1.2",
100 | "lodash": "^4.17.21",
101 | "rxjs": "^7.8.1",
102 | "shell-quote": "^1.8.1",
103 | "supports-color": "^8.1.1",
104 | "tree-kill": "^1.2.2",
105 | "yargs": "^17.7.2"
106 | },
107 | "bin": {
108 | "conc": "dist/bin/concurrently.js",
109 | "concurrently": "dist/bin/concurrently.js"
110 | },
111 | "engines": {
112 | "node": ">=18"
113 | },
114 | "funding": {
115 | "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
116 | }
117 | },
118 | "node_modules/emoji-regex": {
119 | "version": "8.0.0",
120 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
121 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
122 | },
123 | "node_modules/escalade": {
124 | "version": "3.2.0",
125 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
126 | "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
127 | "engines": {
128 | "node": ">=6"
129 | }
130 | },
131 | "node_modules/get-caller-file": {
132 | "version": "2.0.5",
133 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
134 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
135 | "engines": {
136 | "node": "6.* || 8.* || >= 10.*"
137 | }
138 | },
139 | "node_modules/has-flag": {
140 | "version": "4.0.0",
141 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
142 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
143 | "engines": {
144 | "node": ">=8"
145 | }
146 | },
147 | "node_modules/is-fullwidth-code-point": {
148 | "version": "3.0.0",
149 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
150 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
151 | "engines": {
152 | "node": ">=8"
153 | }
154 | },
155 | "node_modules/lodash": {
156 | "version": "4.17.21",
157 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
158 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
159 | },
160 | "node_modules/prettier": {
161 | "version": "3.3.3",
162 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
163 | "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
164 | "bin": {
165 | "prettier": "bin/prettier.cjs"
166 | },
167 | "engines": {
168 | "node": ">=14"
169 | },
170 | "funding": {
171 | "url": "https://github.com/prettier/prettier?sponsor=1"
172 | }
173 | },
174 | "node_modules/require-directory": {
175 | "version": "2.1.1",
176 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
177 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
178 | "engines": {
179 | "node": ">=0.10.0"
180 | }
181 | },
182 | "node_modules/rxjs": {
183 | "version": "7.8.1",
184 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
185 | "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
186 | "dependencies": {
187 | "tslib": "^2.1.0"
188 | }
189 | },
190 | "node_modules/shell-quote": {
191 | "version": "1.8.1",
192 | "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz",
193 | "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==",
194 | "funding": {
195 | "url": "https://github.com/sponsors/ljharb"
196 | }
197 | },
198 | "node_modules/string-width": {
199 | "version": "4.2.3",
200 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
201 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
202 | "dependencies": {
203 | "emoji-regex": "^8.0.0",
204 | "is-fullwidth-code-point": "^3.0.0",
205 | "strip-ansi": "^6.0.1"
206 | },
207 | "engines": {
208 | "node": ">=8"
209 | }
210 | },
211 | "node_modules/strip-ansi": {
212 | "version": "6.0.1",
213 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
214 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
215 | "dependencies": {
216 | "ansi-regex": "^5.0.1"
217 | },
218 | "engines": {
219 | "node": ">=8"
220 | }
221 | },
222 | "node_modules/supports-color": {
223 | "version": "8.1.1",
224 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
225 | "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
226 | "dependencies": {
227 | "has-flag": "^4.0.0"
228 | },
229 | "engines": {
230 | "node": ">=10"
231 | },
232 | "funding": {
233 | "url": "https://github.com/chalk/supports-color?sponsor=1"
234 | }
235 | },
236 | "node_modules/tree-kill": {
237 | "version": "1.2.2",
238 | "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
239 | "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
240 | "bin": {
241 | "tree-kill": "cli.js"
242 | }
243 | },
244 | "node_modules/tslib": {
245 | "version": "2.8.0",
246 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz",
247 | "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA=="
248 | },
249 | "node_modules/wrap-ansi": {
250 | "version": "7.0.0",
251 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
252 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
253 | "dependencies": {
254 | "ansi-styles": "^4.0.0",
255 | "string-width": "^4.1.0",
256 | "strip-ansi": "^6.0.0"
257 | },
258 | "engines": {
259 | "node": ">=10"
260 | },
261 | "funding": {
262 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
263 | }
264 | },
265 | "node_modules/y18n": {
266 | "version": "5.0.8",
267 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
268 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
269 | "engines": {
270 | "node": ">=10"
271 | }
272 | },
273 | "node_modules/yargs": {
274 | "version": "17.7.2",
275 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
276 | "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
277 | "dependencies": {
278 | "cliui": "^8.0.1",
279 | "escalade": "^3.1.1",
280 | "get-caller-file": "^2.0.5",
281 | "require-directory": "^2.1.1",
282 | "string-width": "^4.2.3",
283 | "y18n": "^5.0.5",
284 | "yargs-parser": "^21.1.1"
285 | },
286 | "engines": {
287 | "node": ">=12"
288 | }
289 | },
290 | "node_modules/yargs-parser": {
291 | "version": "21.1.1",
292 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
293 | "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
294 | "engines": {
295 | "node": ">=12"
296 | }
297 | }
298 | }
299 | }
300 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "learning-management-system",
3 | "version": "1.0.0",
4 | "description": "Learning Management System - a web application for managing courses and students",
5 | "directories": {
6 | "doc": "docs"
7 | },
8 | "scripts": {
9 | "install": "cd LMS-Frontend && npm install && cd ../LMS-Backend && pip install -r requirements.txt",
10 | "start": "cd LMS-Backend && python manage.py runserver && cd ../LMS-Frontend && npm start",
11 | "build": "cd LMS-Frontend/app && ng build",
12 | "test": "cd LMS-Frontend/app && ng serve",
13 | "eject": "cd LMS-Frontend/app && npm run eject",
14 | "frontend": "cd LMS-Frontend/app && ng serve",
15 | "backend": "cd LMS-Backend && python manage.py runserver",
16 | "dev": "concurrently \"npm run backend\" \"npm run frontend\"",
17 | "format": "prettier --write \"**/*.{ts,css}\""
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/hoangsonww/Learning-Management-System-Fullstack.git"
22 | },
23 | "bugs": {
24 | "url": "https://github.com/hoangsonww/Learning-Management-System-Fullstack/issues",
25 | "email": "hoangson091104@gmail.com",
26 | "name": "Son Nguyen"
27 | },
28 | "keywords": [
29 | "LMS",
30 | "Learning Management System",
31 | "React",
32 | "Django"
33 | ],
34 | "author": "Son Nguyen",
35 | "license": "MIT",
36 | "dependencies": {
37 | "concurrently": "^9.0.1",
38 | "prettier": "^3.3.3"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------