feat: Add 'test' category for forum topics to separate test content

- Add 'test' to ForumTopic.CATEGORIES with Polish label 'Testowy'
- Add gray styling for test topics (badge + card opacity)
- Add scripts to list and mark test topics
This commit is contained in:
Maciej Pienczyn 2026-01-13 11:48:08 +01:00
parent 8c1f5da5f2
commit 08d6c0b069
6 changed files with 271 additions and 3 deletions

View File

@ -864,14 +864,15 @@ class ForumTopic(Base):
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now) updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
# Constants for validation # Constants for validation
CATEGORIES = ['feature_request', 'bug', 'question', 'announcement'] CATEGORIES = ['feature_request', 'bug', 'question', 'announcement', 'test']
STATUSES = ['new', 'in_progress', 'resolved', 'rejected'] STATUSES = ['new', 'in_progress', 'resolved', 'rejected']
CATEGORY_LABELS = { CATEGORY_LABELS = {
'feature_request': 'Propozycja funkcji', 'feature_request': 'Propozycja funkcji',
'bug': 'Błąd', 'bug': 'Błąd',
'question': 'Pytanie', 'question': 'Pytanie',
'announcement': 'Ogłoszenie' 'announcement': 'Ogłoszenie',
'test': 'Testowy'
} }
STATUS_LABELS = { STATUS_LABELS = {

View File

@ -0,0 +1,69 @@
# Notatka ze spotkania z Arturem
**Data:** 12 stycznia 2026
**Platforma:** Microsoft Teams
---
## Kluczowe ustalenia
### 1. Wizytówka Google jako narzędzie SEO
- Wizytówka Google Business Profile to jedno z narzędzi SEO
- **Do zrobienia:** Pozycjonowanie na local content
- Współpraca: SEO Partners i Pixlab
- Horyzont czasowy: **2 lata**
### 2. Strona WWW
- Skopiować całą stronę, którą zrobił Pixlab
- Dodać: **PKiD**
- Dodać: **Data powstania**
### 3. Model monetyzacji - dostęp do platformy
| Poziom | Cena/mies. | Dostęp |
|--------|------------|--------|
| **Członkowie Izby** | 1 zł | Pełny dostęp do bazy (płacą już ~200 zł składki) |
| **Podstawowy** | 49,99 zł | Podstawowe informacje, baza gospodarcza |
| **Premium** | 99 zł | Dostęp do wszystkiego + dodatkowe funkcjonalności |
- **Współdzielenie kosztów** między uczestnikami
### 4. Reprezentacja firm i treści
- Treści informacyjne "trochę jak MSN.com"
- Do ustalenia szczegóły funkcjonalności dla poziomu Premium
### 5. Agent AI dla portalu - wizja
**Cel:**
- Agent AI, który wie wszystko o organizacjach członkowskich
- Pomaga w kooperacji między firmami
- **Transfer informacji** do innych agentów AI (ChatGPT, Claude, itp.)
**Strategia wdrożenia:**
1. **Najpierw:** Zbudować odpowiedni content dla firm członkowskich
2. **Później:** Poprzez odpowiednie wtyczki/plugins dystrybuować informacje do platform AI
### 6. Case study - Waterm i ChatGPT (historia od Bartka)
> *"Klient (Białorusin) pytany skąd trafił do Waterm odpowiedział: 'W tym ChatGPT to jesteście jedyną najlepszą firmą w Wejherowie. Tylko o Watermie mówią.'"*
**Refleksja Bartka:**
- "Nie wiem, na ile to co robię... powinienem modlić się, żeby konkurencja tego nie robiła jak najdłużej"
- "Ale i tak tej roboty nie jestem w stanie przerobić"
- **Wizja:** "Jeśli wszyscy zadbamy o podniesienie standardu, to ludziom będzie się żyło lepiej"
---
## Do zrobienia (action items)
- [ ] Pozycjonowanie na local content
- [ ] Skopiować strukturę strony Pixlab
- [ ] Zaprojektować poziomy dostępu (1 zł / 49,99 zł / 99 zł)
- [ ] Zbudować content dla firm członkowskich
- [ ] Zaplanować integrację z platformami AI (plugins)
- [ ] Usiąść i poukładać puste elementy
---
## Kluczowy insight
**Waterm jest już widoczny w ChatGPT jako "najlepsza firma w Wejherowie"** - to dowód, że obecność w AI działa. Cel: osiągnąć to samo dla wszystkich firm Norda Biznes.

View File

@ -0,0 +1,40 @@
#!/usr/bin/env python3
"""Lista wszystkich wątków forum z autorami."""
import os
import sys
from dotenv import load_dotenv
# Load .env first
load_dotenv()
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from database import SessionLocal, ForumTopic, ForumReply, User
def main():
db = SessionLocal()
threads = db.query(ForumTopic).order_by(ForumTopic.created_at).all()
print("=== WĄTKI FORUM ===")
print()
for t in threads:
author = db.query(User).filter(User.id == t.author_id).first()
author_name = author.name if author else 'Unknown'
author_email = author.email if author else ''
replies_count = db.query(ForumReply).filter(ForumReply.topic_id == t.id).count()
date_str = t.created_at.strftime('%Y-%m-%d')
print(f"ID: {t.id}")
print(f" Tytuł: {t.title}")
print(f" Autor: {author_name} ({author_email})")
print(f" Data: {date_str}")
print(f" Odpowiedzi: {replies_count}")
print(f" Przypięty: {t.is_pinned}")
print()
db.close()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,45 @@
#!/usr/bin/env python3
"""Oznacz wszystkie obecne wątki forum jako testowe."""
import os
import sys
from dotenv import load_dotenv
# Load .env first
load_dotenv()
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from database import SessionLocal, ForumTopic, ForumReply
def main():
db = SessionLocal()
# Pobierz wszystkie wątki
topics = db.query(ForumTopic).all()
print(f"Znaleziono {len(topics)} wątków do oznaczenia jako testowe...")
print()
for topic in topics:
old_category = topic.category
topic.category = 'test'
print(f" ID {topic.id}: {topic.title[:50]}... ({old_category} → test)")
# Oznacz też odpowiedzi jako AI-generated (jeśli są testowe)
replies = db.query(ForumReply).all()
for reply in replies:
reply.is_ai_generated = True
print()
print(f"Oznaczono {len(topics)} wątków jako 'test'")
print(f"Oznaczono {len(replies)} odpowiedzi jako AI-generated")
db.commit()
db.close()
print()
print("Gotowe!")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""
Rejestracja uczestników z WhatsApp na wydarzenie Chwila dla Biznesu
Tworzy konta użytkowników (bez powiadomień) i zapisuje na wydarzenie.
"""
import os
import sys
import secrets
from datetime import datetime
# Dodaj ścieżkę do modułów
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from database import SessionLocal, Company, User, EventAttendee
from werkzeug.security import generate_password_hash
def main():
db = SessionLocal()
# Dane do utworzenia (imię z WhatsApp, firma, email)
attendees_data = [
("Marcin Bulczak", "Renk Hurtownie", "sekretariat@renk.pl"),
("Agnieszka (Aga)", "Your Welcome", "agnieszka@yourewelcome.pl"),
("Bartosz (Bartek)", "TERMO", "biuro@termocenter.pl"),
("Daniel", "Stalpunkt", "biuro.pomorze@stalpunkt.com"),
("Paweł Cioban", "Kancelaria Ostrowski i Wspólnicy", "p.cioban@ostrowski-legal.net"),
("Sławomir Grula", "Usługi Ogólnobudowlane Sławomir Grula", "slawek1055@onet.pl"),
("Artur Czajkowski (Arturo)", "Family Art", "artczaj@gmail.com"),
]
event_id = 28
print("Tworzę konta użytkowników...")
print("=" * 60)
try:
for name, company_name, email in attendees_data:
# Sprawdź czy użytkownik już istnieje
existing_user = db.query(User).filter(User.email == email).first()
if existing_user:
print(f"{name}: konto już istnieje (ID {existing_user.id})")
user_id = existing_user.id
else:
# Znajdź firmę
company = db.query(Company).filter(Company.name == company_name).first()
# Utwórz użytkownika
temp_password = secrets.token_urlsafe(16)
user = User(
email=email,
name=name,
password_hash=generate_password_hash(temp_password),
is_active=True,
is_admin=False,
company_id=company.id if company else None,
created_at=datetime.now()
)
db.add(user)
db.flush()
print(f"{name}: utworzono konto (ID {user.id}) - {company_name}")
user_id = user.id
# Zapisz na wydarzenie
existing_attendee = db.query(EventAttendee).filter(
EventAttendee.event_id == event_id,
EventAttendee.user_id == user_id
).first()
if existing_attendee:
print(f" → już zapisany na wydarzenie")
else:
attendee = EventAttendee(
event_id=event_id,
user_id=user_id,
registered_at=datetime.now()
)
db.add(attendee)
print(f" → zapisano na wydarzenie 28")
db.commit()
# Podsumowanie
print()
print("=" * 60)
attendees = db.query(EventAttendee).filter(EventAttendee.event_id == event_id).all()
print(f"Uczestnicy wydarzenia 28 ({len(attendees)} osób):")
for a in attendees:
user = db.query(User).filter(User.id == a.user_id).first()
company = db.query(Company).filter(Company.id == user.company_id).first() if user.company_id else None
company_name = company.name if company else "-"
print(f"{user.name} ({company_name})")
except Exception as e:
db.rollback()
print(f"Błąd: {e}")
raise
finally:
db.close()
if __name__ == "__main__":
main()

View File

@ -162,6 +162,17 @@
border-color: #fcd34d; border-color: #fcd34d;
} }
.badge-test {
background: #f3f4f6;
color: #6b7280;
border-color: #d1d5db;
}
.topic-card.test-topic {
opacity: 0.6;
border-left: 4px solid #9ca3af;
}
/* Status badges */ /* Status badges */
.badge-status { .badge-status {
font-size: var(--font-size-xs); font-size: var(--font-size-xs);
@ -349,7 +360,7 @@
{% if topics %} {% if topics %}
<div class="topics-list"> <div class="topics-list">
{% for topic in topics %} {% for topic in topics %}
<article class="topic-card {% if topic.is_pinned %}pinned{% endif %} {% if topic.is_locked %}locked{% endif %}"> <article class="topic-card {% if topic.is_pinned %}pinned{% endif %} {% if topic.is_locked %}locked{% endif %} {% if topic.category == 'test' %}test-topic{% endif %}">
<div class="topic-main"> <div class="topic-main">
<a href="{{ url_for('forum_topic', topic_id=topic.id) }}" class="topic-title"> <a href="{{ url_for('forum_topic', topic_id=topic.id) }}" class="topic-title">
{% if topic.is_pinned %} {% if topic.is_pinned %}