aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2024-12-16 00:41:02 -0500
committerBobby <[email protected]>2024-12-16 00:41:02 -0500
commit04788ddd83c5e80a09a468d07427f0e365db71d7 (patch)
tree5c33a8ba990f21c227dc97b5c76729a39cfe0c67
parent5c14aa56c401915a99cf1c6f5700e8e3cb88453b (diff)
downloadthatcomputerscientist-04788ddd83c5e80a09a468d07427f0e365db71d7.tar.xz
thatcomputerscientist-04788ddd83c5e80a09a468d07427f0e365db71d7.zip
pagoda realm
-rw-r--r--apps/pagoda/admin.py3
-rw-r--r--apps/pagoda/apps.py2
-rw-r--r--apps/pagoda/migrations/0001_initial.py53
-rw-r--r--apps/pagoda/migrations/0002_rename_verficationrecordname_pagodasites_verficationrecordname_and_more.py29
-rw-r--r--apps/pagoda/models.py29
-rw-r--r--apps/pagoda/urls.py11
-rw-r--r--apps/pagoda/views.py186
-rw-r--r--internal/pagoda_utilities.py44
-rw-r--r--middleware/userprofilemiddleware.py4
-rw-r--r--requirements.txt1
-rw-r--r--services/users/migrations/0013_userprofile_balance_userprofile_experience_and_more.py27
-rw-r--r--services/users/migrations/0014_userprofile_journal_streak_and_more.py27
-rw-r--r--services/users/models.py8
-rw-r--r--static/css/pagoda/pagoda.css213
-rw-r--r--static/css/shared/core.css32
-rw-r--r--static/images/pagoda/pagoda_banner.pngbin0 -> 2186420 bytes
-rw-r--r--templates/en/pagoda/home.html213
-rw-r--r--templates/en/pagoda/site_verification.html165
-rw-r--r--templates/ja/pagoda/home.html3
-rw-r--r--templates/shared/left_sidebar.html6
-rw-r--r--templates/shared/right_sidebar.html12
-rw-r--r--thatcomputerscientist/settings.py2
-rw-r--r--thatcomputerscientist/urls.py1
23 files changed, 1060 insertions, 11 deletions
diff --git a/apps/pagoda/admin.py b/apps/pagoda/admin.py
index 8c38f3f3..94502d0f 100644
--- a/apps/pagoda/admin.py
+++ b/apps/pagoda/admin.py
@@ -1,3 +1,6 @@
from django.contrib import admin
# Register your models here.
+from apps.pagoda.models import PagodaSites
+
+admin.site.register(PagodaSites)
diff --git a/apps/pagoda/apps.py b/apps/pagoda/apps.py
index acc7a0eb..54a5ecdd 100644
--- a/apps/pagoda/apps.py
+++ b/apps/pagoda/apps.py
@@ -3,4 +3,4 @@ from django.apps import AppConfig
class PagodaConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
- name = "pagoda"
+ name = "apps.pagoda"
diff --git a/apps/pagoda/migrations/0001_initial.py b/apps/pagoda/migrations/0001_initial.py
new file mode 100644
index 00000000..9ba0607e
--- /dev/null
+++ b/apps/pagoda/migrations/0001_initial.py
@@ -0,0 +1,53 @@
+# Generated by Django 5.0.7 on 2024-12-16 02:41
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="PagodaSites",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("name", models.CharField(max_length=50)),
+ ("description", models.TextField(blank=True)),
+ ("url", models.URLField()),
+ ("verified", models.BooleanField(default=False)),
+ ("siteUniqueIdentifier", models.TextField(unique=True)),
+ ("VerficationRecordName", models.CharField(blank=True, max_length=50)),
+ (
+ "VerificationRecordValue",
+ models.CharField(blank=True, max_length=50),
+ ),
+ ("created_at", models.DateTimeField(auto_now_add=True)),
+ ("updated_at", models.DateTimeField(auto_now=True)),
+ (
+ "owner",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ ],
+ options={
+ "verbose_name_plural": "Pagoda Sites",
+ "ordering": ["-created_at"],
+ },
+ ),
+ ]
diff --git a/apps/pagoda/migrations/0002_rename_verficationrecordname_pagodasites_verficationrecordname_and_more.py b/apps/pagoda/migrations/0002_rename_verficationrecordname_pagodasites_verficationrecordname_and_more.py
new file mode 100644
index 00000000..2a22d9a4
--- /dev/null
+++ b/apps/pagoda/migrations/0002_rename_verficationrecordname_pagodasites_verficationrecordname_and_more.py
@@ -0,0 +1,29 @@
+# Generated by Django 5.0.7 on 2024-12-16 02:48
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("pagoda", "0001_initial"),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name="pagodasites",
+ old_name="VerficationRecordName",
+ new_name="verficationRecordName",
+ ),
+ migrations.RenameField(
+ model_name="pagodasites",
+ old_name="VerificationRecordValue",
+ new_name="verificationRecordValue",
+ ),
+ migrations.AddField(
+ model_name="pagodasites",
+ name="verificationMethod",
+ field=models.CharField(
+ choices=[("DNS", "DNS"), ("Meta", "Meta")], default="DNS", max_length=4
+ ),
+ ),
+ ]
diff --git a/apps/pagoda/models.py b/apps/pagoda/models.py
index 71a83623..11657953 100644
--- a/apps/pagoda/models.py
+++ b/apps/pagoda/models.py
@@ -1,3 +1,32 @@
from django.db import models
+from django.conf import settings
+
# Create your models here.
+class PagodaSites(models.Model):
+ owner = models.ForeignKey(
+ settings.AUTH_USER_MODEL,
+ on_delete=models.CASCADE,
+ )
+ name = models.CharField(max_length=50)
+ description = models.TextField(blank=True)
+ url = models.URLField()
+ verified = models.BooleanField(default=False)
+ siteUniqueIdentifier = models.TextField(unique=True)
+ verificationMethod = models.CharField(
+ max_length=4, choices=((x, x) for x in ["DNS", "Meta"]), default="DNS"
+ )
+ verficationRecordName = models.CharField(max_length=50, blank=True)
+ verificationRecordValue = models.CharField(max_length=50, blank=True)
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ def __str__(self):
+ return self.name
+
+ def get_sites_created_by_user(user):
+ return PagodaSites.objects.filter(owner=user)
+
+ class Meta:
+ verbose_name_plural = "Pagoda Sites"
+ ordering = ["-created_at"]
diff --git a/apps/pagoda/urls.py b/apps/pagoda/urls.py
new file mode 100644
index 00000000..1ca11d2d
--- /dev/null
+++ b/apps/pagoda/urls.py
@@ -0,0 +1,11 @@
+from django.urls import path
+
+from . import views
+
+app_name = "pagoda"
+urlpatterns = [
+ path("", views.home, name="home"),
+ path("m/<str:site_id>", views.site_dashboard, name="site"),
+ path("m/<str:site_id>/status", views.check_verification_status, name="site_status"),
+ path("m/<str:site_id>/delete", views.delete_site, name="delete_site"),
+]
diff --git a/apps/pagoda/views.py b/apps/pagoda/views.py
index 91ea44a2..ad8f3079 100644
--- a/apps/pagoda/views.py
+++ b/apps/pagoda/views.py
@@ -1,3 +1,189 @@
from django.shortcuts import render
+from thatcomputerscientist.utils import i18npatterns
+from apps.pagoda.models import PagodaSites
+from django.contrib.auth.decorators import login_required
+from internal.pagoda_utilities import (
+ pagoda_unique_site_id_generator,
+ pagoda_verification_record_generator,
+ pagoda_url_sanitizer,
+)
+from django.http import Http404, HttpResponseRedirect
+from django.urls import reverse
+from django.contrib import messages
+import dns.resolver
+import requests
+from bs4 import BeautifulSoup
+
# Create your views here.
+@login_required
+def home(request):
+ META = {
+ "title": "The Pagoda Realm",
+ }
+ LANGUAGE_CODE = i18npatterns(request.LANGUAGE_CODE)
+ request.meta.update(META)
+
+ pagoda_sites = PagodaSites.get_sites_created_by_user(request.user)
+ if request.method == "GET":
+ context = {
+ "pagoda_sites": pagoda_sites,
+ }
+ return render(request, f"{LANGUAGE_CODE}/pagoda/home.html", context)
+ elif request.method == "POST":
+ name = request.POST.get("site_name")
+ url = request.POST.get("root_url")
+ url = pagoda_url_sanitizer(url)
+ verificationMethod = request.POST.get("verification_method")
+ siteUniqueIdentifier = pagoda_unique_site_id_generator()
+ verficationRecordName, verificationRecordValue = (
+ pagoda_verification_record_generator(verificationMethod)
+ )
+ if not url:
+ messages.error(
+ request,
+ "The URL provided is not valid. Please provide a valid URL.",
+ extra_tags="pagodaError",
+ )
+ return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
+
+ if url.replace("http://", "").replace("https://", "") in [
+ site.url.replace("http://", "").replace("https://", "")
+ for site in pagoda_sites
+ ]:
+ messages.error(
+ request,
+ "This site can not be added to The Pagoda Realm at this moment. If you alredy have this site listed in The Pagoda Realm, please verify it or delete and re-add the website if you wish to change the verification method.",
+ extra_tags="pagodaError",
+ )
+ return HttpResponseRedirect(request.META.get("HTTP_REFERER"))
+
+ PagodaSites.objects.create(
+ owner=request.user,
+ name=name,
+ url=url,
+ siteUniqueIdentifier=siteUniqueIdentifier,
+ verificationMethod=verificationMethod,
+ verficationRecordName=verficationRecordName,
+ verificationRecordValue=verificationRecordValue,
+ )
+
+ return HttpResponseRedirect(reverse("pagoda:home"))
+ else:
+ pass
+
+ return render(request, f"{LANGUAGE_CODE}/pagoda/home.html")
+
+
+@login_required
+def site_dashboard(request, site_id):
+ LANGUAGE_CODE = i18npatterns(request.LANGUAGE_CODE)
+ site = PagodaSites.objects.get(siteUniqueIdentifier=site_id, owner=request.user)
+ context = {
+ "site": site,
+ }
+ META = {
+ "title": f"Manage {site.name} — The Pagoda Realm",
+ }
+ request.meta.update(META)
+ if site.verified:
+ return render(
+ request, f"{LANGUAGE_CODE}/pagoda/site_verification.html", context
+ )
+ elif not site.verified:
+ return render(
+ request, f"{LANGUAGE_CODE}/pagoda/site_verification.html", context
+ )
+ else:
+ return Http404()
+
+
+@login_required
+def check_verification_status(request, site_id):
+ site = PagodaSites.objects.get(siteUniqueIdentifier=site_id, owner=request.user)
+
+ if site.verified:
+ messages.success(
+ request,
+ "The site has been verified successfully.",
+ extra_tags="pagodaSuccess",
+ )
+ return HttpResponseRedirect(reverse("pagoda:site", args=[site_id]))
+
+ if site.verificationMethod == "DNS":
+ domain = site.url.replace("http://", "").replace("https://", "")
+ txt_name = site.verficationRecordName
+ txt_value = site.verificationRecordValue
+ domain = txt_name + "." + domain.split("/")[0]
+
+ try:
+ txt_records = dns.resolver.resolve(domain, "TXT")
+ for txt_record in txt_records:
+ for txt_string in txt_record.strings:
+ if txt_string.decode("utf-8") == txt_value:
+ site.verified = True
+ site.save()
+ messages.success(
+ request,
+ "The site has been verified successfully.",
+ extra_tags="pagodaSuccess",
+ )
+ return HttpResponseRedirect(
+ reverse("pagoda:site", args=[site_id])
+ )
+ except dns.resolver.NoAnswer:
+ pass
+ messages.error(
+ request,
+ "The site could not be verified. Please check the DNS records and try again.",
+ extra_tags="pagodaError",
+ )
+ return HttpResponseRedirect(reverse("pagoda:site", args=[site_id]))
+ elif site.verificationMethod == "Meta":
+ domain = site.url
+ response = requests.get(domain)
+ soup = BeautifulSoup(response.text, "html.parser")
+ meta_tags = soup.find_all("meta")
+ for meta_tag in meta_tags:
+ if (
+ meta_tag.get("name") == site.verficationRecordName
+ and meta_tag.get("content") == site.verificationRecordValue
+ ):
+ site.verified = True
+ site.save()
+ messages.success(
+ request,
+ "The site has been verified successfully.",
+ extra_tags="pagodaSuccess",
+ )
+ return HttpResponseRedirect(reverse("pagoda:site", args=[site_id]))
+ messages.error(
+ request,
+ "The site could not be verified. Please check the Meta tags and try again.",
+ extra_tags="pagodaError",
+ )
+ return HttpResponseRedirect(reverse("pagoda:site", args=[site_id]))
+ else:
+ pass
+
+ return Http404()
+
+
+@login_required
+def delete_site(request, site_id):
+ site = PagodaSites.objects.get(siteUniqueIdentifier=site_id, owner=request.user)
+ if site:
+ site.delete()
+ messages.success(
+ request,
+ "The site has been deleted successfully.",
+ extra_tags="pagodaSuccess",
+ )
+ return HttpResponseRedirect(reverse("pagoda:home"))
+ else:
+ messages.error(
+ request,
+ "The site could not be deleted. Please try again.",
+ extra_tags="pagodaError",
+ )
+ return HttpResponseRedirect(reverse("pagoda:home"))
diff --git a/internal/pagoda_utilities.py b/internal/pagoda_utilities.py
new file mode 100644
index 00000000..37c5ca7e
--- /dev/null
+++ b/internal/pagoda_utilities.py
@@ -0,0 +1,44 @@
+from apps.pagoda.models import PagodaSites
+import random
+import string
+import re
+
+
+def pagoda_unique_site_id_generator():
+ site_id = "".join(random.choices(string.ascii_letters + string.digits, k=16))
+ if PagodaSites.objects.filter(siteUniqueIdentifier=site_id).exists():
+ return pagoda_unique_site_id_generator()
+ return site_id
+
+
+def pagoda_verification_record_generator(record_type):
+ if record_type == "DNS":
+ txtName = "".join(random.choices(string.ascii_letters + string.digits, k=8))
+ txtValue = "".join(random.choices(string.ascii_letters + string.digits, k=48))
+ return txtName, txtValue
+ elif record_type == "Meta":
+ metaName = "".join(random.choices(string.ascii_letters + string.digits, k=8))
+ metaValue = "".join(random.choices(string.ascii_letters + string.digits, k=48))
+ return metaName, metaValue
+ else:
+ return None, None
+
+
+def pagoda_url_sanitizer(url):
+ if not url.startswith("http://") and not url.startswith("https://"):
+ url = f"http://{url}"
+
+ # Validate if the URL is valid
+ regex = re.compile(
+ r"^(?:http|ftp)s?://"
+ r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}|"
+ r"localhost|"
+ r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"
+ r"(?::\d+)?"
+ r"(?:/?|[/?]\S+)$",
+ re.IGNORECASE,
+ )
+
+ if not regex.match(url):
+ return None
+ return url
diff --git a/middleware/userprofilemiddleware.py b/middleware/userprofilemiddleware.py
index dc6c8bac..96a7997e 100644
--- a/middleware/userprofilemiddleware.py
+++ b/middleware/userprofilemiddleware.py
@@ -1,5 +1,6 @@
from django.utils.deprecation import MiddlewareMixin
from services.users.models import UserProfile
+from apps.blog.models import Post
class UserProfileMiddleware(MiddlewareMixin):
@@ -7,5 +8,8 @@ class UserProfileMiddleware(MiddlewareMixin):
if request.user.is_authenticated:
user_profile = UserProfile.objects.get(user=request.user)
request.user.profile = user_profile
+ request.user.profile.weblogs_created = Post.objects.filter(
+ author=request.user
+ ).count()
else:
request.user.profile = None
diff --git a/requirements.txt b/requirements.txt
index d8b03c97..424177b1 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,5 @@
django
+dnspython
django-redis
python-dotenv
gunicorn
diff --git a/services/users/migrations/0013_userprofile_balance_userprofile_experience_and_more.py b/services/users/migrations/0013_userprofile_balance_userprofile_experience_and_more.py
new file mode 100644
index 00000000..a7b2c565
--- /dev/null
+++ b/services/users/migrations/0013_userprofile_balance_userprofile_experience_and_more.py
@@ -0,0 +1,27 @@
+# Generated by Django 5.0.7 on 2024-12-16 02:27
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("users", "0012_alter_tokenstore_expires"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="userprofile",
+ name="balance",
+ field=models.IntegerField(default=500),
+ ),
+ migrations.AddField(
+ model_name="userprofile",
+ name="experience",
+ field=models.IntegerField(default=0),
+ ),
+ migrations.AddField(
+ model_name="userprofile",
+ name="level",
+ field=models.IntegerField(default=1),
+ ),
+ ]
diff --git a/services/users/migrations/0014_userprofile_journal_streak_and_more.py b/services/users/migrations/0014_userprofile_journal_streak_and_more.py
new file mode 100644
index 00000000..81a9c90b
--- /dev/null
+++ b/services/users/migrations/0014_userprofile_journal_streak_and_more.py
@@ -0,0 +1,27 @@
+# Generated by Django 5.0.7 on 2024-12-16 02:32
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("users", "0013_userprofile_balance_userprofile_experience_and_more"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="userprofile",
+ name="journal_streak",
+ field=models.IntegerField(default=0),
+ ),
+ migrations.AddField(
+ model_name="userprofile",
+ name="journal_streak_best",
+ field=models.IntegerField(default=0),
+ ),
+ migrations.AddField(
+ model_name="userprofile",
+ name="journal_streak_reset",
+ field=models.BooleanField(default=False),
+ ),
+ ]
diff --git a/services/users/models.py b/services/users/models.py
index f11f2f46..31e7f291 100644
--- a/services/users/models.py
+++ b/services/users/models.py
@@ -14,7 +14,13 @@ class UserProfile(models.Model):
is_public = models.BooleanField(default=False)
email_public = models.BooleanField(default=False)
email_verified = models.BooleanField(default=False)
- blinkie_url = models.TextField(blank=True, default='')
+ blinkie_url = models.TextField(blank=True, default="")
+ experience = models.IntegerField(default=0)
+ level = models.IntegerField(default=1)
+ balance = models.IntegerField(default=500)
+ journal_streak = models.IntegerField(default=0)
+ journal_streak_reset = models.BooleanField(default=False)
+ journal_streak_best = models.IntegerField(default=0)
def __str__(self):
return self.user.username
diff --git a/static/css/pagoda/pagoda.css b/static/css/pagoda/pagoda.css
new file mode 100644
index 00000000..c8f3469a
--- /dev/null
+++ b/static/css/pagoda/pagoda.css
@@ -0,0 +1,213 @@
+/* Main Container */
+.pagoda-realm {
+ background-color: rgba(134, 99, 229, 0.1);
+ border: 1px solid #8d8dff;
+ border-radius: 8px;
+ padding: 24px;
+ margin: 16px 0;
+ position: relative;
+ overflow: hidden;
+}
+
+.pagoda-realm::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 2px;
+ background: linear-gradient(90deg, transparent, #df23c4, transparent);
+ animation: scanline 2s linear infinite;
+}
+
+/* Banner */
+.pagoda-banner {
+ width: 100%;
+ max-height: 200px;
+ object-fit: cover;
+ border-radius: 4px;
+ margin-bottom: 16px;
+}
+
+/* Typography */
+.pagoda-realm h2 {
+ color: #8d8dff;
+ margin: 24px 0 16px;
+ padding-bottom: 8px;
+ border-bottom: 1px solid rgba(141, 141, 255, 0.3);
+}
+
+.pagoda-realm h3 {
+ color: #da73ff;
+ margin: 16px 0 12px;
+}
+
+.pagoda-realm p {
+ margin: 12px 0;
+ line-height: 1.6;
+}
+
+.pagoda-realm em {
+ color: #df23c4;
+ font-style: normal;
+ font-weight: 500;
+}
+
+.pagoda-realm strong {
+ color: #da73ff;
+ font-weight: 600;
+}
+
+/* Lists */
+.pagoda-realm ol,
+.pagoda-realm ul {
+ margin: 16px 0;
+ padding-left: 24px;
+}
+
+.pagoda-realm li {
+ margin: 8px 0;
+ line-height: 1.5;
+}
+
+/* Table Styles */
+.pagoda-realm table {
+ width: 100%;
+ border-collapse: collapse;
+ margin: 16px 0;
+ background: rgba(0, 0, 0, 0.2);
+}
+
+.pagoda-realm th,
+.pagoda-realm td {
+ padding: 12px;
+ border: 1px solid rgba(141, 141, 255, 0.3);
+ text-align: left;
+}
+
+.pagoda-realm th {
+ background: rgba(134, 99, 229, 0.2);
+ color: #8d8dff;
+ font-weight: 600;
+}
+
+.pagoda-realm td {
+ background: rgba(0, 0, 0, 0.1);
+}
+
+.pagoda-realm td a {
+ color: #8d8dff;
+ text-decoration: none;
+}
+
+.pagoda-realm td a:hover {
+ color: #df23c4;
+ text-decoration: underline;
+}
+
+/* Forms */
+.pagoda-dashboard-empty form {
+ background: rgba(98, 55, 149, 0.2);
+ padding: 16px;
+ border-radius: 4px;
+ margin: 16px 0;
+}
+
+.pagoda-realm label {
+ display: block;
+ margin: 12px 0 4px;
+ color: #da73ff;
+ font-weight: 500;
+}
+
+.pagoda-realm input[type="text"],
+.pagoda-realm select {
+ width: 100%;
+ padding: 8px 12px;
+ background: rgba(0, 0, 0, 0.3);
+ border: 1px solid #8663e5;
+ border-radius: 4px;
+ color: #fff;
+ margin-bottom: 8px;
+}
+
+.pagoda-realm input[type="text"]:focus,
+.pagoda-realm select:focus {
+ border-color: #df23c4;
+ box-shadow: 0 0 8px rgba(223, 35, 196, 0.3);
+}
+
+.pagoda-realm select {
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%238d8dff' viewBox='0 0 16 16'%3E%3Cpath d='M8 10l4-4H4z'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 12px center;
+ padding-right: 36px;
+}
+
+/* Note Text */
+.pagoda-realm .note {
+ font-size: 0.9em;
+ color: rgba(255, 255, 255, 0.7);
+ font-style: italic;
+ margin: 4px 0 12px;
+}
+
+/* Buttons */
+.pagoda-realm button {
+ background: linear-gradient(45deg, #8663e5, #df23c4);
+ border: none;
+ padding: 12px 24px;
+ border-radius: 4px;
+ color: #fff;
+ font-weight: 500;
+ cursor: pointer;
+ margin: 8px 0;
+ transition: all 0.3s ease;
+}
+
+.pagoda-realm button:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 0 15px rgba(223, 35, 196, 0.5);
+}
+
+/* Dashboard Sites Table */
+.pagoda-dashboard-sites table {
+ margin-bottom: 24px;
+}
+
+.pagoda-dashboard-sites td {
+ text-align: left;
+}
+
+/* Error Messages */
+.pagoda-realm [class*="Error"] {
+ color: #ff9a9a;
+ background: rgba(255, 0, 0, 0.1);
+ padding: 8px 12px;
+ border-radius: 4px;
+ margin: 8px 0;
+}
+
+/* Animation */
+@keyframes scanline {
+ 0% {
+ transform: translateX(-100%);
+ }
+
+ 100% {
+ transform: translateX(100%);
+ }
+}
+
+/* Responsive Adjustments */
+@media (max-width: 1200px) {
+ .pagoda-realm {
+ margin: 16px;
+ }
+
+ .pagoda-realm table {
+ display: block;
+ overflow-x: auto;
+ }
+} \ No newline at end of file
diff --git a/static/css/shared/core.css b/static/css/shared/core.css
index 94e9e2e1..ac629b03 100644
--- a/static/css/shared/core.css
+++ b/static/css/shared/core.css
@@ -63,6 +63,38 @@ a:active {
color: #df23c4;
}
+button {
+ background: #4444b1;
+ color: #fff;
+ padding: 8px 16px;
+ border-radius: 4px;
+ cursor: pointer;
+ outline: none;
+ font-weight: bold;
+}
+
+button:hover {
+ background: #df23c4;
+}
+
+button:active {
+ background: #8d8dff;
+}
+
+button:disabled {
+ background: #8d8dff;
+ color: #ccc;
+ cursor: not-allowed;
+}
+
+button:disabled:hover {
+ background: #8d8dff;
+}
+
+button:focus {
+ outline: none;
+}
+
a:hover {
text-decoration: underline;
}
diff --git a/static/images/pagoda/pagoda_banner.png b/static/images/pagoda/pagoda_banner.png
new file mode 100644
index 00000000..5d3b11a3
--- /dev/null
+++ b/static/images/pagoda/pagoda_banner.png
Binary files differ
diff --git a/templates/en/pagoda/home.html b/templates/en/pagoda/home.html
new file mode 100644
index 00000000..d5910d11
--- /dev/null
+++ b/templates/en/pagoda/home.html
@@ -0,0 +1,213 @@
+{% extends 'shared/base.html' %}
+{% load static %}
+{% block head %}
+<link rel="stylesheet" href="{% static 'css/pagoda/pagoda.css' %}">
+{% endblock head %}
+{% block content %}
+<div class="pagoda-realm">
+{% if not request.COOKIES.visitedPagodaRealm %}
+ <img src="{% static 'images/pagoda/pagoda_banner.png' %}" alt="The Pagoda Realm Banner" class="pagoda-banner">
+ <hr>
+ <div class="pagoda-introduction-text">
+ <h2>Welcome to The Pagoda Realm, {{ user.first_name }}!</h2>
+ <p>What is The Pagoda Realm, you might ask? If you recall, it was a service in the list of services that I promised to
+ provide access you to when you signed up for an account. But in reality, I lied to you. The Pagoda Realm is not a
+ single service, but a <em>collection</em> of services that I have carefully designed to help you in with your
+ <em>Neocities</em> and other retro static site hosting projects like <em>Nekoweb</em>. The web is a canvas, and you
+ are the artist, limited only by your imagination. The Pagoda Realm is a place where you can integrate various tools
+ on your own website, which would have been otherwise hard to do.
+ </p>
+ <p>All services are built to be very simple to use and beginner-friendly. A lot of these services provide data in JSON
+ and as well as prerendered iframes, so you can use them in your own website without having to worry about the
+ technical details. The components are designed to be fashionable and usually support variety of themes and
+ custom CSS to match your website's design.
+ </p>
+ <h3>How do I use the services in The Pagoda Realm?</h3>
+ <p>Using the services in The Pagoda Realm is very simple. If you are looking at this page, that means that you have already
+ cleared the first step, which is to sign up for an account. Once you have signed up for an account, you need to connect
+ your website to The Pagoda Realm. This can be done in two ways:</p>
+ <ol>
+ <li><strong>Using DNS Verification</strong> - This is the recommended method. You can add a TXT record to your website's
+ DNS settings to verify that you own the website. Once the DNS record is added, you can connect your website to The
+ Pagoda Realm.</li>
+ <li><strong>Using Meta Tag Verification</strong> - This is the alternative method if you are using a host like Neocities
+ or Nekoweb and do not have access to the DNS settings. You can add a meta tag to your website's HTML to verify that
+ you own the website. Once the meta tag is added, you can connect your website to The Pagoda Realm.</li>
+ </ol>
+ <p>Once your website is connected to The Pagoda Realm, you can start using the services. You can create a journal, write a
+ weblog post, add comments to your weblog post, join the webring, add entries to the guestbook, show ads on your website,
+ and view the number of visitors that have visited your website. You can also embed the services on your website using
+ the JSON API or the prerendered iframe.</p>
+ <h3>What services are available in The Pagoda Realm?</h3>
+ <p>Here are some of the services that are available in The Pagoda Realm:</p>
+ <table>
+ <thead>
+ <tr>
+ <th>Service</th>
+ <th>Description</th>
+ <th>Embeddable as</th>
+ <th>API</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>Journals</td>
+ <td>A daily diary like journal that you can use to write about your day, thoughts, and feelings.</td>
+ <td>JSON, iframe</td>
+ <td>Yes</td>
+ </tr>
+ <tr>
+ <td>Weblogs</td>
+ <td>A simple weblog service that you can use to write articles, stories, and blog posts.</td>
+ <td>JSON, iframe</td>
+ <td>Yes</td>
+ </tr>
+ <tr>
+ <td>Weblog Comments</td>
+ <td>A simple comment system that you can use to add comments to your weblog posts.</td>
+ <td>JSON, iframe</td>
+ <td>Yes</td>
+ </tr>
+ <tr>
+ <td>Caravan</td>
+ <td>A webring service that binds all the websites registered on The Pagoda Realm together.</td>
+ <td>iframe</td>
+ <td>No</td>
+ </tr>
+ <tr>
+ <td>Ledger</td>
+ <td>Ledger is a guestbook service that you can use to receive feedback and comments from your website visitors.</td>
+ <td>JSON, iframe</td>
+ <td>Yes</td>
+ </tr>
+ <tr>
+ <td>Pamphlet</td>
+ <td>Pamphlet is a ad-simulation service which shows random fake period specific ads on your website.</td>
+ <td>JavaScript</td>
+ <td>No</td>
+ </tr>
+ <tr>
+ <td>Census</td>
+ <td>Census is a service that shows the number of visitors that have visited your website.</td>
+ <td>iframe</td>
+ <td>Yes</td>
+ </tr>
+ </tbody>
+ </table>
+ <ul>
+ <li><strong>Journals</strong> - A daily diary like journal that you can use to write about your day, thoughts, and
+ feelings. Journals are both private and public, and you can create multiple journals for different purposes.
+ Public journals can be embedded on your website. You will be able to view your journal entries in a calendar
+ view, and you can also export your journal entries in JSON format. Furthermore, you can also use the journal
+ entries in your own website using the JSON API.</li>
+ <li><strong>Weblogs</strong> - A simple weblog service that you can use to write articles, stories, and blog posts.
+ Weblogs are always public by default (unless in draft mode), and you can only have one weblog per account. You
+ can categorize your posts, tag them, and edit the post in full HTML mode and even change CSS on per post basis.
+ You can also embed your weblog posts on your website using the JSON API or the prerendered iframe.</li>
+ <li><strong>Weblog Comments</strong> - A simple comment system that you can use to add comments to your weblog posts.
+ You can customize the comment system to match your website's design, and you can also moderate the comments
+ before they are published. The comment system is also available as a JSON API and prerendered iframe.</li>
+ <li><strong>Caravan</strong> - A webring service that binds all the websites registered on The Pagoda Realm together.
+ You automatically become a member of the webring when you link your website to the Pagoda Realm. You can also
+ upload custom banner image for your website preview, embed the badge on your website, and view the list of
+ websites in the webring. The webring is also available as a prerendered iframe.</li>
+ <li><strong>Ledger</strong> - Ledger is a guestbook service that you can use to receive feedback and comments from
+ your website visitors. You can customize the guestbook to match your website's design, and you can moderate the
+ entries before they are published. The guestbook is also available as a JSON API and prerendered iframe.</li>
+ <li><strong>Pamphlet</strong> - Pamphlet is a ad-simulation service which shows random fake period specific ads on your
+ website. Pamphlet can show banner ads and square ads. It can also show 88 x 31 pixel buttons. It is available as a
+ JavaScript library which you need to initialize on your website.</li>
+ <li><strong>Census</strong> - Census is a service that shows the number of visitors that have visited your website. It
+ can show the number of visitors in the last 24 hours, the last 7 days, and the total number of visitors. Census is
+ available as a prerendered iframe and as a JSON API.</li>
+ </ul>
+ <p>These are just some of the services that are available in The Pagoda Realm. I will be adding more services in the
+ future, so stay tuned for more updates. If you have any questions or feedback, please feel free to contact me. I hope
+ you enjoy using The Pagoda Realm!</p>
+ <p>Thank you for using The Pagoda Realm!</p>
+ <button onclick="document.cookie = 'visitedPagodaRealm=true; expires=Fri, 31 Dec 9999 23:59:59 GMT'; window.location.reload();">I Understand What I Must Do!</button>
+ </div>
+{% else %}
+ {% if not pagoda_sites %}
+ <div class="pagoda-dashboard-empty">
+ <h2>Welcome to The Pagoda Realm, {{ user.first_name }}!</h2>
+ <p>It looks like you have not connected any websites to The Pagoda Realm yet. To get started, you need to connect your
+ website to The Pagoda Realm. This can be done in two ways:</p>
+ <ol>
+ <li><strong>Using DNS Verification</strong> - This is the recommended method. You can add a TXT record to your website's
+ DNS settings to verify that you own the website. Once the DNS record is added, you can connect your website to The
+ Pagoda Realm.</li>
+ <li><strong>Using Meta Tag Verification</strong> - This is the alternative method if you are using a host like Neocities
+ or Nekoweb and do not have access to the DNS settings. You can add a meta tag to your website's HTML to verify that
+ you own the website. Once the meta tag is added, you can connect your website to The Pagoda Realm.</li>
+ </ol>
+ <h2>Connect Your Website to The Pagoda Realm</h2>
+ <form action="{% url "pagoda:home" %}" method="post">
+ {% csrf_token %}
+ <label for="site_name">Site Name</label>
+ <input type="text" name="site_name" id="site_name" placeholder="My Website">
+ <label for="root_url">Root URL</label>
+ <input type="text" name="root_url" id="root_url" placeholder="https://mysite.neocities.org">
+ <p class="note">Note: The root URL should be the URL of your website's homepage. It can also be your custom domain
+ if you have one.</p>
+ <label for="verification_method">Verification Method</label>
+ <select name="verification_method" id="verification_method">
+ <option value="DNS">DNS Verification</option>
+ <option value="Meta">Meta Tag Verification</option>
+ </select>
+ <button type="submit">Connect Website</button>
+ </form>
+ </div>
+ {% else %}
+ <div class="pagoda-dashboard-sites">
+ <h2 style="margin: 8px 0px;">Your Connected Websites</h2>
+ <table>
+ <thead>
+ <tr>
+ <th>Site Name</th>
+ <th>Root URL</th>
+ <th>Verified</th>
+ <th>Actions</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for site in pagoda_sites %}
+ <tr>
+ <td><a href="{% url "pagoda:site" site.siteUniqueIdentifier %}">{{ site.name }}</a></td>
+ <td style="text-align: center;"><a href="{{ site.url }}" target="_blank">{{ site.url }}</a></td>
+ <td style="text-align: center;">{% if site.verified %}✅{% else %}❌{% endif %}</td>
+ <td style="text-align: center;">
+ <a href="{% url "pagoda:site" site.siteUniqueIdentifier %}">Manage Site</a>
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ <div class="pagoda-dashboard-empty">
+ <h2>Connect Another Website to The Pagoda Realm</h2>
+ <form action="{% url "pagoda:home" %}" method="post">
+ {% csrf_token %}
+ <label for="site_name">Site Name</label>
+ <input type="text" name="site_name" id="site_name" placeholder="My Website">
+ <label for="root_url">Root URL</label>
+ <input type="text" name="root_url" id="root_url" placeholder="https://mysite.neocities.org">
+ <p class="note">Note: The root URL should be the URL of your website's homepage. It can also be your custom domain
+ if you have one.</p>
+ <label for="verification_method">Verification Method</label>
+ <select name="verification_method" id="verification_method">
+ <option value="DNS">DNS Verification</option>
+ <option value="Meta">Meta Tag Verification</option>
+ </select>
+ <button type="submit">Connect Website</button>
+ </form>
+ {% for message in messages %}
+ {% if 'pagodaError' in message.tags %}
+ <p style="color: #ff9a9a;">{{ message }}</p>
+ {% endif %}
+ {% endfor %}
+ </div>
+ </div>
+ {% endif %}
+{% endif %}
+</div>
+{% endblock content %} \ No newline at end of file
diff --git a/templates/en/pagoda/site_verification.html b/templates/en/pagoda/site_verification.html
new file mode 100644
index 00000000..9d5e1aad
--- /dev/null
+++ b/templates/en/pagoda/site_verification.html
@@ -0,0 +1,165 @@
+{% extends 'shared/base.html' %}
+{% load static %}
+{% block head %}
+<style>
+.verification-container {
+ background-color: rgba(134, 99, 229, 0.1);
+ border: 1px solid #8d8dff;
+ border-radius: 8px;
+ padding: 24px;
+ margin: 16px 0;
+ position: relative;
+ overflow: hidden;
+ }
+
+ .verification-container::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 2px;
+ background: linear-gradient(90deg, transparent, #df23c4, transparent);
+ animation: scanline 2s linear infinite;
+ }
+
+ /* Page Title */
+ .verification-container h1 {
+ color: #fff;
+ margin-bottom: 12px;
+ text-shadow: 0 0 10px #8663e5;
+ }
+
+ /* Section Headers */
+ .verification-container h2 {
+ color: #8d8dff;
+ margin: 12px 0 12px 0;
+ padding-bottom: 8px;
+ border-bottom: 1px solid rgba(141, 141, 255, 0.3);
+ }
+
+ /* Status Indicators */
+ .verification-status {
+ display: inline-block;
+ padding: 4px 12px;
+ border-radius: 4px;
+ font-weight: 500;
+ margin: 8px 4px;
+ }
+
+ .status-verified {
+ background-color: rgba(0, 255, 0, 0.2);
+ border: 1px solid #00ff00;
+ }
+
+ .status-not-verified {
+ background-color: rgba(255, 0, 0, 0.2);
+ border: 1px solid #ff0000;
+ }
+
+ /* Code Display */
+ .verification-container code {
+ background-color: rgba(0, 0, 0, 0.3);
+ padding: 0px 12px;
+ border-radius: 4px;
+ border: 1px solid #8663e5;
+ display: inline-block;
+ margin: 8px 0;
+ font-family: monospace;
+ font-size: 12px;
+ }
+
+ /* Record Display */
+ .record-details {
+ background-color: rgba(98, 55, 149, 0.2);
+ padding: 16px;
+ border-radius: 4px;
+ margin: 16px 0;
+ }
+
+ .record-details p {
+ margin: 8px 0;
+ }
+
+ /* Buttons */
+ .verification-container button {
+ background: linear-gradient(45deg, #8663e5, #df23c4);
+ border: none;
+ padding: 12px 24px;
+ border-radius: 4px;
+ color: #fff;
+ font-weight: 500;
+ cursor: pointer;
+ margin: 8px 8px 8px 0;
+ transition: all 0.3s ease;
+ }
+
+ .verification-container button:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 0 15px rgba(223, 35, 196, 0.5);
+ }
+
+ .delete-button {
+ background: linear-gradient(45deg, #ff3366, #ff0000) !important;
+ }
+
+ /* Strong Tags */
+ .verification-container strong {
+ color: #da73ff;
+ font-weight: 600;
+ }
+
+ /* Animation */
+ @keyframes scanline {
+ 0% {
+ transform: translateX(-100%);
+ }
+ 100% {
+ transform: translateX(100%);
+ }
+ }
+
+ /* Responsive Adjustments */
+ @media (max-width: 1200px) {
+ .verification-container {
+ margin: 16px;
+ }
+ }
+</style>
+{% endblock head %}
+{% block content %}
+<div class="verification-container">
+ <h1>Verification Details for {{ site.name }}</h1>
+ <p>Here are the details you need to verify your site with The Pagoda Realm:</p>
+ <p><strong>Verification Method:</strong> {% if site.verificationMethod == 'DNS' %}DNS{% else %}Meta Tag{% endif %}</p>
+ <div class="record-details">
+ {% if site.verificationMethod == 'DNS' %}
+ <p><strong>TXT Record Name:</strong> {{ site.verficationRecordName }}</p>
+ <p><strong>TXT Record Value:</strong> {{ site.verificationRecordValue }}</p>
+ {% else %}
+ <p><strong>Meta Tag:</strong><code>&lt;meta name="{{ site.verficationRecordName }}" content="{{ site.verificationRecordValue }}"&gt;</code></p>
+ {% endif %}
+ </div>
+ <p>
+ <strong>Verification Status:</strong>
+ <span class="verification-status {% if site.verified %}status-verified{% else %}status-not-verified{% endif %}">
+ {% if site.verified %}Verified{% else %}Not Verified{% endif %}
+ </span>
+ </p>
+
+ <h2>Verification Instructions</h2>
+ <p>To verify your site, you need to add the following details to your site's code:</p>
+ {% if site.verificationMethod == 'DNS' %}
+ <p>Log in to your domain registrar's control panel and add a new TXT record with the following details:</p>
+ <p><strong>Record Name:</strong> {{ site.verficationRecordName }}</p>
+ <p><strong>Record Value:</strong> {{ site.verificationRecordValue }}</p>
+ {% else %}
+ <p>Open your site's HTML code and add the following meta tag to the <code>&lt;head&gt;</code> section:</p>
+ <p><code>&lt;meta name="{{ site.verficationRecordName }}" content="{{ site.verificationRecordValue }}"&gt;</code></p>
+ {% endif %}
+ <p>Once you've added the details, click the button below to check the verification status.</p>
+
+ <button class="verify-button" onclick="window.location.href = '{% url 'pagoda:site_status' site.siteUniqueIdentifier %}'">Check Verification Status</button>
+ <button class="delete-button" onclick="window.location.href = '{% url 'pagoda:delete_site' site.siteUniqueIdentifier %}'">Delete Site</button>
+</div>
+{% endblock %}
diff --git a/templates/ja/pagoda/home.html b/templates/ja/pagoda/home.html
new file mode 100644
index 00000000..1a0f2414
--- /dev/null
+++ b/templates/ja/pagoda/home.html
@@ -0,0 +1,3 @@
+{% extends 'shared/base.html' %}
+{% load static %}
+Welcome to the \ No newline at end of file
diff --git a/templates/shared/left_sidebar.html b/templates/shared/left_sidebar.html
index 1603f5f1..5815b373 100644
--- a/templates/shared/left_sidebar.html
+++ b/templates/shared/left_sidebar.html
@@ -19,7 +19,7 @@
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}" />
<input type="text" id="username" name="username" placeholder="{% if request.LANGUAGE_CODE == 'ja' %}ユーザー名{% else %}Username{% endif %}" autocomplete="off" value="{{ request.GET.username }}" />
<input type="password" id="password" name="password" placeholder="{% if request.LANGUAGE_CODE == 'ja' %}パスワード{% else %}Password{% endif %}" autocomplete="off" />
- <input type="hidden" name="next" value="{{ request.path }}" />
+ <input type="hidden" name="next" value="{% if request.GET.next %}{{ request.GET.next }}{% else %}{{ request.path }}{% endif %}" />
<input type="submit" value="" />
</form>
<a href="#" id="register-now-button"></a>
@@ -38,7 +38,7 @@
</div>
<div class="user-item">
<img src="{% static 'images/core/icons/pagodarealm.png' %}" alt="The Pagoda Realm Icon" />
- <a href="#pagodarealm">{% if request.LANGUAGE_CODE == 'ja' %}パゴダレルム{% else %}The Pagoda Realm{% endif %}</a>
+ <a href="{% url "pagoda:home" %}">{% if request.LANGUAGE_CODE == 'ja' %}パゴダレルム{% else %}The Pagoda Realm{% endif %}</a>
</div>
<div class="user-item">
<img src="{% static 'images/core/icons/mypage.png' %}" alt="My Page Icon" />
@@ -59,7 +59,7 @@
<div class="navigation-items-container">
<div class="navigation-item">
<img src={% static 'images/core/icons/home.png' %} alt="Home Icon" />
- <a href="#home">{% if request.LANGUAGE_CODE == 'ja' %}ホーム{% else %}Home{% endif %}</a>
+ <a href="{% url "core:home" %}">{% if request.LANGUAGE_CODE == 'ja' %}ホーム{% else %}Home{% endif %}</a>
</div>
<div class="navigation-item">
<img src="{% static 'images/core/icons/journalofrandomthoughts.png' %}" alt="Journal of Random Thoughts Icon" />
diff --git a/templates/shared/right_sidebar.html b/templates/shared/right_sidebar.html
index 3a071310..1c9375cd 100644
--- a/templates/shared/right_sidebar.html
+++ b/templates/shared/right_sidebar.html
@@ -53,30 +53,30 @@
<p class="stats-bio">{{ user.profile.bio|linebreaksbr }}</p>
<div class="user-stats">
<p class="stat-name">{% if request.LANGUAGE_CODE == 'ja' %}経験値{% else %}XP{% endif %}</p>
- <p class="stat-value">{{ user.profile.experience }}0 / 1000</p>
+ <p class="stat-value">{{ user.profile.experience }} / 1000</p>
</div>
<div class="user-stats">
<p class="stat-name">{% if request.LANGUAGE_CODE == 'ja' %}レベル{% else %}Level{% endif %}</p>
- <p class="stat-value">{{ user.profile.level }}1</p>
+ <p class="stat-value">{{ user.profile.level }}</p>
</div>
<div class="user-stats">
<p class="stat-name">{% if request.LANGUAGE_CODE == 'ja' %}残高{% else %}Balance{% endif %}</p>
<p class="stat-value">
- <span>{{ user.profile.balance }}0</span>
+ <span>{{ user.profile.balance }}</span>
<img src="{% static 'images/core/icons/coin.png' %}" alt="Coin Icon" />
</p>
</div>
<div class="user-stats">
<p class="stat-name">{% if request.LANGUAGE_CODE == 'ja' %}ジャーナルエントリー{% else %}Journal Entries{% endif %}</p>
- <p class="stat-value">{{ user.profile.journal_entries }}0</p>
+ <p class="stat-value">{{ user.profile.journal_entries }}</p>
</div>
<div class="user-stats">
<p class="stat-name">{% if request.LANGUAGE_CODE == 'ja' %}ジャーナル連続記録{% else %}Journal Streak{% endif %}</p>
- <p class="stat-value">{{ user.profile.journal_streak }}0 days</p>
+ <p class="stat-value">{{ user.profile.journal_streak }} days</p>
</div>
<div class="user-stats">
<p class="stat-name">{% if request.LANGUAGE_CODE == 'ja' %}ウェブログ投稿{% else %}Weblog Posts{% endif %}</p>
- <p class="stat-value">{{ user.profile.weblog_posts }}0</p>
+ <p class="stat-value">{{ user.profile.weblogs_created }}</p>
</div>
<div class="stat-links">
<a href="#">{% if request.LANGUAGE_CODE == 'ja' %}ドレッシングルーム{% else %}Dressing Room{% endif %}</a>
diff --git a/thatcomputerscientist/settings.py b/thatcomputerscientist/settings.py
index f8cd493e..ca703842 100644
--- a/thatcomputerscientist/settings.py
+++ b/thatcomputerscientist/settings.py
@@ -20,6 +20,7 @@ load_dotenv()
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
+LOGIN_URL = "/"
# set n_connected_lc_users to 0 on startup
@@ -78,6 +79,7 @@ INSTALLED_APPS = [
"apps.core",
"apps.administration",
"apps.blog",
+ "apps.pagoda",
"services.users",
"services.stream",
"services.pamphlet",
diff --git a/thatcomputerscientist/urls.py b/thatcomputerscientist/urls.py
index 754ebd50..ad9dee50 100644
--- a/thatcomputerscientist/urls.py
+++ b/thatcomputerscientist/urls.py
@@ -41,6 +41,7 @@ handler404 = "thatcomputerscientist.error_handler.custom_404"
urlpatterns = [
path("", include("apps.core.urls", namespace="core")),
path("weblog/", include("apps.blog.urls", namespace="weblog")),
+ path("pagoda/", include("apps.pagoda.urls", namespace="pagoda")),
path("services/stream/", include("services.stream.urls", namespace="stream")),
path("services/pamphlet", include("services.pamphlet.urls", namespace="pamphlet")),
path("services/auth/", include("services.users.urls", namespace="auth")),