Show HN: Wewod – A Social Score Tracker for CrossFit WODs (wewod.app)

3 points by iami ↗ HN
Hi HN!

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
Для этого проекта я могу помочь с базовой структурой кода, чтобы создать серверную часть с возможностью создания, редактирования и удаления опросов, с учетом требований, которые ты указал. Давай рассмотрим версию с использованием `MySQL` для хранения данных. Мы будем использовать `Node.js` и `Express.js` для создания API и `MySQL` для базы данных.

### Структура проекта

Проект будет включать следующие основные файлы: - `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); },

  update: (id, title, description, callback) => {
    const query = 'UPDATE surveys SET title = ?, description = ?, editable = ? WHERE id = ? AND editable = true';
    db.query(query, [title, description, false, id], callback);
  },

  delete: (id, callback) => {
    const query = 'DELETE FROM surveys WHERE id = ?';
    db.query(query, [id], callback);
  },

  getAll: (callback) => {
    const query = 'SELECT * FROM surveys';
    db.query(query, 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...

(comment deleted)
Для проекта на JavaScript с использованием JSON как базы данных, мы создадим базовую структуру с серверной частью на Node.js и Express.js, где данные опросов хранятся в JSON-файле. Вот как это может выглядеть:

### Структура проекта

``` project/ ├── data/ │ └── surveys.json ├── app.js └── package.json ```

### Шаг 1: Инициализация проекта

1. Создаем папку проекта и переходим в неё:

   ```bash
   mkdir project
   cd project
   ```
2. Инициализируем npm:

   ```bash
   npm init -y
   ```
3. Устанавливаем Express.js:

   ```bash
   npm install express
   ```
### Шаг 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);

  if (surveyIndex === -1) {
    return res.status(404).json({ error: 'Survey not found' });
  }

  if (!surveys[surveyIndex].editable) {
    return res.status(403).json({ error: 'Survey cannot be edited' });
  }

  surveys[surveyIndex] = { ...surveys[surveyIndex], ...req.body, editable: false };
  writeSurveys(surveys);
  res.json(surveys[surveyIndex]);
});

// Удаление опроса app.delete('/surveys/:id', (req, res) => { const surveys = readSurveys(); const surveyId = parseInt(req.params.id); const newSurveys = surveys.filter(survey => survey.id !== surveyId);

  if (newSurveys.length === surveys.length) {
    return res.status(404).json({ error: 'Survey not found' });
  }

  writeSurveys(newSurveys);
  res.status(204).send();
});

// Запуск сервера const PORT = 3000; app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); }); ```

### Пояснение к коду:

1. *Маршруты*: - `POST /surveys` – создание нового опроса с полем `editable: true`. - `GET /...

Добавим необходимые функции: статистику опроса, авторизацию для админа и подключение к базе данных MySQL. Начнем с MySQL и дампа базы, затем добавим логику авторизации и статистику.

### Шаг 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);

  jwt.verify(token, SECRET_KEY, (err, user) => {
    if (err) return res.sendStatus(403);
    req.user = user;
    next();
  });
}; ```

Теперь добавьте `authenticateToken` как middleware к защищенным маршрутам, например:

```javascript app.post('/surveys', authenticateToken, (req, res) => { /* код создания опроса / }); ```

### Шаг 4: Добавляем статистику опроса

Создаем маршрут для получения статистики. Этот маршрут считает количество ответов для каждого опроса.

```javascript 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...
Вот полный код `app.js` без комментариев:

```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);

  jwt.verify(token, SECRET_KEY, (err, user) => {
    if (err) return res.sendStatus(403);
    req.user = user;
    next();
  });
};

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` на ваши данные.