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:
parent
8c1f5da5f2
commit
08d6c0b069
@ -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 = {
|
||||||
|
|||||||
69
docs/meetings/2026-01-12-artur-teams.md
Normal file
69
docs/meetings/2026-01-12-artur-teams.md
Normal 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.
|
||||||
40
scripts/list_forum_topics.py
Normal file
40
scripts/list_forum_topics.py
Normal 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()
|
||||||
45
scripts/mark_test_topics.py
Normal file
45
scripts/mark_test_topics.py
Normal 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()
|
||||||
102
scripts/register_whatsapp_attendees.py
Normal file
102
scripts/register_whatsapp_attendees.py
Normal 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()
|
||||||
@ -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 %}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user