Show HN: Wewod – A Social Score Tracker for CrossFit WODs (wewod.app)
I built Wewod to provide athletes worldwide with access to a global leaderboard of scores for well-established benchmarks in the CrossFit and functional fitness communities.
What sets Wewod apart from traditional tracking apps is that it combines a logbook with social network features. While most apps focus on individual tracking or offer paid training programs, Wewod is dedicated solely to benchmarking. This makes it an accessible tool for both amateur and professional athletes to compare their performance.
My motivation came from a passion for the sport and some personal frustration - after trying several popular apps, I found their UIs complex and limited by paywalls. I wanted to create an app with a simple, fun, and intuitive user interface.
I’d love for you to try it out and share feedback on the user experience. Even if you’re not familiar with this niche, your input would still be invaluable.
Tech Stack:
- App: React Native, Expo, React Relay
- Backend: Self-hosted Hasura with a custom persisted query layer using Nginx and Lua; Express; Firebase (authentication only); Cloud SQL
- Website: NextJS
- Infrastructure: Google Cloud, Terraform
- Observability: Datadog
Thanks for your time and feedback!
5 comments
[ 3.1 ms ] story [ 24.2 ms ] thread### Структура проекта
Проект будет включать следующие основные файлы: - `server.js`: Основной файл сервера. - `db.js`: Файл для настройки подключения к базе данных. - `models/surveyModel.js`: Модель для работы с таблицей опросов. - `routes/surveyRoutes.js`: Роуты для CRUD-операций с опросами.
### Шаги по реализации
#### 1. Установка зависимостей В папке проекта установим необходимые модули:
```bash npm init -y npm install express mysql2 ```
#### 2. Настройка подключения к базе данных (`db.js`)
Создадим файл `db.js` для подключения к MySQL.
```javascript // db.js const mysql = require('mysql2'); const connection = mysql.createConnection({ host: 'localhost', user: 'root', password: 'yourpassword', database: 'survey_db' });
connection.connect((err) => { if (err) throw err; console.log('Connected to MySQL database'); });
module.exports = connection; ```
#### 3. Модель опроса (`models/surveyModel.js`)
Создадим модель для взаимодействия с таблицей опросов.
```javascript // models/surveyModel.js const db = require('../db');
const Survey = { create: (title, description, callback) => { const query = 'INSERT INTO surveys (title, description, editable) VALUES (?, ?, ?)'; db.query(query, [title, description, true], callback); },
};module.exports = Survey; ```
#### 4. Роуты для опросов (`routes/surveyRoutes.js`)
Создадим роуты для работы с опросами.
```javascript // routes/surveyRoutes.js const express = require('express'); const router = express.Router(); const Survey = require('../models/surveyModel');
// Создать новый опрос router.post('/create', (req, res) => { const { title, description } = req.body; Survey.create(title, description, (err, results) => { if (err) return res.status(500).json(err); res.status(201).json({ message: 'Survey created', surveyId: results.insertId }); }); });
// Обновить опрос (доступно только один раз) router.put('/update/:id', (req, res) => { const { id } = req.params; const { title, description } = req.body; Survey.update(id, title, description, (err, results) => { if (err) return res.status(500).json(err); if (results.affectedRows === 0) return res.status(403).json({ message: 'Survey c...
### Структура проекта
``` project/ ├── data/ │ └── surveys.json ├── app.js └── package.json ```
### Шаг 1: Инициализация проекта
1. Создаем папку проекта и переходим в неё:
2. Инициализируем npm: 3. Устанавливаем Express.js: ### Шаг 2: Создаем файл `surveys.json` для хранения опросовСоздаем папку `data` и файл `surveys.json` в ней с начальным содержимым:
```json [] ```
Это будет массив объектов, где каждый объект — отдельный опрос.
### Шаг 3: Создаем файл `app.js` для серверной логики
Вот код для `app.js`:
```javascript const express = require('express'); const fs = require('fs'); const app = express();
app.use(express.json());
const surveysFilePath = './data/surveys.json';
// Функция для чтения данных из файла const readSurveys = () => { const data = fs.readFileSync(surveysFilePath, 'utf-8'); return JSON.parse(data); };
// Функция для записи данных в файл const writeSurveys = (surveys) => { fs.writeFileSync(surveysFilePath, JSON.stringify(surveys, null, 2)); };
// Создание нового опроса app.post('/surveys', (req, res) => { const surveys = readSurveys(); const newSurvey = { id: Date.now(), ...req.body, editable: true }; surveys.push(newSurvey); writeSurveys(surveys); res.status(201).json(newSurvey); });
// Получение всех опросов app.get('/surveys', (req, res) => { const surveys = readSurveys(); res.json(surveys); });
// Редактирование опроса (если он редактируемый) app.put('/surveys/:id', (req, res) => { const surveys = readSurveys(); const surveyId = parseInt(req.params.id); const surveyIndex = surveys.findIndex(survey => survey.id === surveyId);
});// Удаление опроса app.delete('/surveys/:id', (req, res) => { const surveys = readSurveys(); const surveyId = parseInt(req.params.id); const newSurveys = surveys.filter(survey => survey.id !== surveyId);
});// Запуск сервера const PORT = 3000; app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); }); ```
### Пояснение к коду:
1. *Маршруты*: - `POST /surveys` – создание нового опроса с полем `editable: true`. - `GET /...
### Шаг 1: Настройка базы данных MySQL и дампа
1. Создайте базу данных MySQL и таблицу `surveys`.
```sql CREATE DATABASE survey_db; USE survey_db;
CREATE TABLE surveys ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, editable BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
CREATE TABLE responses ( id INT AUTO_INCREMENT PRIMARY KEY, survey_id INT, response_text TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (survey_id) REFERENCES surveys(id) ); ```
2. Чтобы создать дамп базы данных, используйте команду:
```bash mysqldump -u your_username -p survey_db > survey_db_dump.sql ```
### Шаг 2: Настройка соединения с MySQL в `app.js`
Установите модуль `mysql2`:
```bash npm install mysql2 ```
В `app.js` добавим подключение к базе данных:
```javascript const mysql = require('mysql2');
// Подключение к базе данных const db = mysql.createConnection({ host: 'localhost', user: 'your_username', password: 'your_password', database: 'survey_db' });
db.connect(err => { if (err) throw err; console.log('Connected to MySQL database'); }); ```
### Шаг 3: Добавляем авторизацию для админа
Используем простую авторизацию через JWT. Установите `jsonwebtoken`:
```bash npm install jsonwebtoken ```
Добавьте секретный ключ:
```javascript const jwt = require('jsonwebtoken'); const SECRET_KEY = 'your_secret_key'; ```
Добавьте эндпоинт для логина и middleware для защиты маршрутов:
```javascript // Эндпоинт для логина app.post('/login', (req, res) => { const { username, password } = req.body; if (username === 'admin' && password === 'password') { // Используйте более безопасный метод в реальном проекте const token = jwt.sign({ username }, SECRET_KEY, { expiresIn: '1h' }); res.json({ token }); } else { res.status(401).json({ error: 'Invalid credentials' }); } });
// Middleware для проверки токена const authenticateToken = (req, res, next) => { const token = req.headers['authorization']; if (!token) return res.sendStatus(401);
}; ```Теперь добавьте `authenticateToken` как middleware к защищенным маршрутам, например:
```javascript app.post('/surveys', authenticateToken, (req, res) => { /* код создания опроса / }); ```
### Шаг 4: Добавляем статистику опроса
Создаем маршрут для получения статистики. Этот маршрут считает количество ответов для каждого опроса.
```javascript app.get('/surveys/:id/statistics', authenticateToken, (req, res) => { const surveyId = req.params.id;
```javascript const express = require('express'); const fs = require('fs'); const jwt = require('jsonwebtoken'); const mysql = require('mysql2'); const app = express();
app.use(express.json());
const SECRET_KEY = 'your_secret_key';
const db = mysql.createConnection({ host: 'localhost', user: 'your_username', password: 'your_password', database: 'survey_db' });
db.connect(err => { if (err) throw err; console.log('Connected to MySQL database'); });
const authenticateToken = (req, res, next) => { const token = req.headers['authorization']; if (!token) return res.sendStatus(401);
};app.post('/login', (req, res) => { const { username, password } = req.body; if (username === 'admin' && password === 'password') { const token = jwt.sign({ username }, SECRET_KEY, { expiresIn: '1h' }); res.json({ token }); } else { res.status(401).json({ error: 'Invalid credentials' }); } });
app.post('/surveys', authenticateToken, (req, res) => { const { title } = req.body; db.query('INSERT INTO surveys (title) VALUES (?)', [title], (err, result) => { if (err) throw err; res.status(201).json({ id: result.insertId, title, editable: true }); }); });
app.get('/surveys', authenticateToken, (req, res) => { db.query('SELECT * FROM surveys', (err, results) => { if (err) throw err; res.json(results); }); });
app.put('/surveys/:id', authenticateToken, (req, res) => { const { title } = req.body; const surveyId = req.params.id; db.query('SELECT editable FROM surveys WHERE id = ?', [surveyId], (err, results) => { if (err) throw err; if (!results[0].editable) { return res.status(403).json({ error: 'Survey cannot be edited' }); } db.query('UPDATE surveys SET title = ?, editable = FALSE WHERE id = ?', [title, surveyId], (err) => { if (err) throw err; res.json({ id: surveyId, title, editable: false }); }); }); });
app.delete('/surveys/:id', authenticateToken, (req, res) => { const surveyId = req.params.id; db.query('DELETE FROM surveys WHERE id = ?', [surveyId], (err, result) => { if (err) throw err; res.status(204).send(); }); });
app.get('/surveys/:id/statistics', authenticateToken, (req, res) => { const surveyId = req.params.id; db.query('SELECT COUNT(*) AS response_count FROM responses WHERE survey_id = ?', [surveyId], (err, results) => { if (err) throw err; res.json({ surveyId, response_count: results[0].response_count }); }); });
const PORT = 3000; app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); }); ```
Замените `your_username`, `your_password`, и `survey_db` на ваши данные.