Запуск власного стартапу, технічна складова, виклики та не технічні особливості

💡 Усі статті, обговорення, новини про Mobile — в одному місці. Приєднуйтесь до Mobile спільноти!

Вітаю, спільното DOU.

Мене звати Роман (www.linkedin.com/in/romanmalko). Я засновник mmeet.app. У цій статті хочу поділитись технічними викликами, як реалізував проєкт, та нетехнічними, з чим стикнувся.

(happy path відео є на сайті mmeet.app)

Ідея

Усі знайомства починаються однаково: матч → чат → «привіт, як справи» → через три тижні нічого. Я хотів інакше: ніяких чатів. Сподобалась людина — відразу пропонуєш, де і коли зустрітись. Вона погоджується або пропонує своє. Мета — від матчу до реальної зустрічі за 5 хвилин.

Основа проєкту — mmeet-video: замість набору фото кожен записує короткі відповіді на кілька питань; проєкт склеює їх в одне безперервне відео. Дивишся — і відразу бачиш живу людину, а не відфільтровану картинку.

Цю статтю я писав, щоб показати, як усе вкладено під капот і на чому я спотикався.

Репо — монорепо з кількома частинами:

  • мобільний застосунок — React Native Expo
  • сайт — Next.js лендінг
  • основний бекенд — Spring Boot + GraphQL, єдина публічна точка входу
  • воркер для склейки відео — Spring Boot сервіс без HTTP
  • photo-validation — окремий сервіс, який перевіряє фото користувача через Gemini
  • video-validation — окремий сервіс, який перевіряє записане відео через Gemini

Плюс Nginx спереду і docker-compose для прод-розгортання.

Схема трафіку проста:

Мобілка і сайт ніколи не йдуть напряму в базу, Redis чи S3. Тільки через основний бекенд. Це економить купу нервів — один вхід, одне місце для логіки, один шар безпеки.

Авторизація — чому я пішов від Firebase Auth

Спочатку логін був через Firebase Auth. Зручно, але кожна дрібниця — через їхню чорну скриньку. Я виніс auth на бекенд:

  • Email + пароль — BCrypt на бекенді, при першому логіні автоматично реєструється користувач. Невідомий email і неправильний пароль повертають однаковий 401 — щоб не можна було вгадувати, хто зареєстрований.
  • Google — мобілка тягне idToken через Google SDK, надсилає на бекенд. Бекенд перевіряє підпис через Google-верифікатор, дістає email з claims.
  • Apple — те саме через Apple JWKS.

Після будь-якого з трьох варіантів я видаю свій JWT на 30 днів. Firebase Admin залишив тільки для push-нотифікацій, там він незамінний.

Чому так краще: контроль над тим, як ведуться сесії, що записується в лог, і не треба гуглити «firebase auth custom claim» щотижня.

GraphQL і кодоген — контракт, який сам себе оновлює

У мене одна схема на бекенді. Мобілка запускає yarn codegen — і з цієї схеми генерується типізована обгортка для Apollo. Тобто хуки для мутацій, запитів і типи всіх полів — усе автоматом.

Підводний камінь тут один, але великий: якщо забув запустити codegen після зміни схеми, типи стають застарілими, і TypeScript тобі не скаже. Тому правило: змінив бекенд → yarn codegen на мобілці. Записав, щоб не забувалося.

Як працює головна фіча — домовитись про зустріч

Весь флоу крутиться навколо об’єкта «зустріч». У нього три слоти адреси:

  • адреса, яку запропонував ініціатор
  • адреса, яку запропонувала інша сторона (якщо контрпропозиція)
  • узгоджена адреса, якщо дійшли згоди

Послідовність:

  1. Користувач А через Google Places вибирає місце і час. Я зберігаю не що завгодно, а тільки координати і адресу, які віддав сам Google — мобілка не може підсунути вигадану адресу.
  2. Бекенд записує пропозицію і шле push користувачу Б.
  3. Б тисне «Accept». Бекенд переносить пропозицію в статус узгодженої, шле push А.

mmeet-video: склейка через чергу

Це найцікавіша частина технічно. Користувач записує відповіді на питання — кожна відповідь як окремий mp4, мобілка шле їх на основний бекенд, той кладе файли в S3. Після кожної відповіді треба склеїти всі відеофайли в єдине відео.

Тільки от склейка через ffmpeg — довга, і на основному бекенді її робити не можна (забив потік — інші користувачі чекають, або взагалі сервер падає із-за нехватки пам’яті). Тому:

  1. Основний бекенд пише в Redis свіжий timestamp для користувача.
  2. Кидає повідомлення в RabbitMQ з цим самим timestamp.
  3. Воркер склейки (окремий сервіс, без HTTP взагалі) забирає повідомлення.
  4. Перед склейкою звіряє: «мій timestamp == той, що в Redis?»
  5. Якщо так — качає всі відповіді з S3, запускає ffmpeg, заливає результат назад в S3, пушить мобілку.
  6. Якщо ні — мовчки викидає повідомлення.

Навіщо ця перевірка? Якщо користувач перезаписав відповідь, поки йшла стара склейка, — стара вже неактуальна. Простий порівняльний timestamp у Redis робить дедуплікацію без блокувань і без складних стейтів.

Контракт повідомлення живе в двох місцях — на стороні продюсера (основний бекенд) і на стороні консюмера (воркер). Змінив, з одного боку, забув, з іншого — відео просто перестає склеюватися. Тому я окремо записав попередження про те, щоб тримати їх синхронно. Також сервіс по склейці можна масштабувати, бо він підключається до брокерів і працює асинхронно.

Валідація фото і відео через Gemini

Коли користувач заливає фото чи записує відео — це ще не означає, що воно придатне для застосунку. Там може бути що завгодно: скріншот з інтернету, не обличчя, відверта картинка, темрява, три людини в кадрі замість однієї.

Я зробив два окремі сервіси — photo-validation і video-validation. Логіка однакова:

  1. Основний бекенд після завантаження файлу кидає задачу в чергу.
  2. Відповідний сервіс забирає файл з S3, надсилає його в Gemini з промптом.
  3. Gemini віддає структуровану відповідь: валідно / невалідно + причина.
  4. Сервіс пише результат назад, і бекенд знає — показувати цю фотку/відео іншим чи просити перезняти.

Навіщо окремі сервіси: по-перше, виклики до Gemini повільні й іноді падають — не хочу, щоб це блокувало основний потік. По-друге, я можу використовувати різні промпти і різні моделі незалежно, не чіпаючи бекенд.

Що я наламав і як полагодив

Фото, які вбивали сервер

Спершу мобілка заливала фото «як є» — якість 1.0, без обмеження розмірів. 8-мегапіксельна селфі з iPhone — це 4–6 МБ.

Виправлення на дві сторони:

  • Мобілка: якість стиснення 0,8, максимальна сторона 1080 пікселів. Типове фото тепер 150–300 КБ.
  • Бекенд: ліміт 2 МБ, плюс перевірка magic-байтів. Тобто недостатньо сказати в Content-Type «я jpeg» — сервер читає перші байти файлу і дивиться, чи це реально JPEG/PNG/WebP. Інакше будь-хто міг би завантажити .exe з підробленим заголовком.

Maestro e2e — як дістати код верифікації з Redis

E2E-тести зроблені на Maestro. Дуже зручна робота з сценаріями. Detox не завівся.

JDK 24 зламав Android білд

Одного ранку Android перестав збиратися з restricted method in java.lang.System. Виявилося — одна з RN-бібліотек використовує CMake-плагін, який падає на JDK 24. Лікування: поставити Temurin 17 і виставити JAVA_HOME на нього. RN поки не дружить з найновішою Java.

Підсумок

Головні уроки, які я для себе виніс:

  1. Один вхід робить життя простим. Всі запити йдуть через нього.
  2. GraphQL + codegen економить купу часу, але вимагає дисципліни запускати codegen.
  3. Фонова робота — в чергу. Склейка відео і валідація через чергу набагато стійкіші, ніж крутити це в веб-потоці.
  4. Валідуй на обох сторонах. Мобілка стискає фото, бекенд перевіряє розмір і магічні байти. Жодна зі сторін не довіряє іншій.
  5. Не бійся відмовитися від хайпового SDK. Firebase Auth — класний, але свій JWT простіший і під контролем.

Про нетехнічну сторону

Я зрозумів, що робота над своїм стартапом — це насправді робота над собою, над своїм перфекціонізмом, який змушує вичищати усе до пікселя, і при цьому треба балансувати з продуктивністю і рухатись вперед.

Стартап як дитина.

Є ще одна частина цього досвіду, про яку хочеться сказати окремо — не технічна, а людська.

Власний стартап — це як власна дитина. Ти крутишся навколо нього, щось доробляєш, хочеш покращити, вичищаєш, доводиш до ідеалу. І в цьому постійному поліруванні є своя пастка: поруч може жити страх опублікувати його у світ, по-справжньому відпустити. Бо відпустити — це переорієнтуватися з ролі автора на роль глядача. Вже не ти створюєш кожен піксель, а ти спостерігаєш, як люди ним користуються, і лише обережно коригуєш динаміку потоку в те русло, яке вважаєш правильним.

Це внутрішній перехід, який технічно описати важко, але він реальний. Ти можеш нескінченно переписувати той самий екран, міняти відтінок оранжевого на чекмарку, додавати ще один edge case у валідації — і все це під приводом ще не готово. Насправді ж готовність — це не стан продукту, це твоя згода відпустити.

Окремо — про відповідальність. Коли людина пропонує стартап, вона по суті пропонує простір, у якому панують певні правила та цінності, ваші правила та цінності, які вам близькі. Соціальний застосунок — це не просто код і сервери. Це середовище, у якому люди зустрічаються, говорять одне з одним, довіряють одне одному свої обличчя й голоси. І це велика відповідальність автора — мати благородні наміри для цього простору і застосовувати інструменти, щоб тримати в ньому порядок.

У випадку mmeet це означає серйозно ставитися до модерації, до приватності, до того, як саме люди представлені одне одному, до того, які сигнали підсилює алгоритм, а які ні. Це не додаткова фіча — це фундамент. Технології тут інструмент, а не мета. Мета — простір, у якому людям добре бути собою.

Що далі

Зараз фокус на налаштуванні моніторингу через Grafana, покращенні video-merger pipeline (паралельна обробка) і експериментах з AI для автоматичної модерації відео.

Застосунок проходить перевірку в маркетах Apple App Store та Google Play.

👍ПодобаєтьсяСподобалось5
До обраногоВ обраному0
LinkedIn
Ctrl + Enter
Ctrl + Enter

Експериментуємо щоб подивитись на дейтинг ринок під іншим кутом, візуалізувати потік уваги та мотивації користувача. Вибрали розглянути його на прикладі електро схеми бо це найточніша аналогія — струм як увага людини. Що з цього вийшло:

blog.mmeet.app/...​market-as-circuit?lang=ua

Реалізували сьогодні функціонал тимчасових чатів, які відкриваються за годину до зустрічі, щоб простіше було знайтися (зарелізимо ближчим часом).

Випадково усвідомили: весь функціонал Tinder-чату — typing indicators, read receipts, локація, фото, presence — у нас живе у модалці, що відкривається на 2 години навколо зустрічі і саморуйнується. Те, що там — основний продукт, тут — короткий помічник перед зустріччю.

youtube.com/shorts/pHvQ-hR93Y0

У мене є 2 питання:
1. Планується монетизація? Я прочитав статю, і нічого про це не почув. А це досить важливо для стартапу.
2. Що статовно просування?

Ці 2 питання, не штики у вас, мені самому дійсно цікаво робити щось своє, просто цікаво як з цим справи у інших.
Стосовно монетизації, я можу вам трошки підказати, а там приймати це чи ні, вирішувати вам. Дуже класний варіант — RevenueCat. У них зручне SDK, дуже багато аналітики своєї + пейвольчики які можна переробити під себе, як зробив я наприклад.
res.cloudinary.com/...​/wr1yt6zzv7trhkl0ljfo.png

1. Так, думав про монетизацію та зупиняюсь поки на класичній моделі з кредитами. Є безкоштовні кожного тижня та додаткові платні. Підписку поки немає сенсу вводити, почати з кредитів, .
2. Тут складніше. Немає зараз бюджету на рекламу тому поки серафанне радіо та просування в інформаційних каналах.

Стосовно ліби — Дякую, прийму до уваги

Це зараз більше як пет проєкт, тому поки бюджет на рекламу не розглядається

Мінус Firebase auth не в чорній скриньці чи необхідності гуглити, а в прив’язці до firebase та ціни питання, коли кількість юзерів стане більше ххх.

Веб версію додано. Це аналог мобільного додатку у вебі з такою самою функціональністю 👍

Роман, це не стартап, а пет проект.
У стартапа задача. — заробити гроші, тут задача інша.

А як пет проект — красивий, можна гратись

Тіндер із відосами. А як оригінальний додасть таку функцію ?

НЕ дуже зрозумів що ви маєте на увазі «оригінальний додасть таку функцію?». Перефразуйте якщо можете. Але якщо я правильно зрозумів, то в тіндера так є така функція, але ніхто з великих не зробив відео основою профілю. Це і є ніша mmeet — не «є відео як опція», а «відео замість профілю взагалі».

Це чудово, нехай щастить!!

Сьогодні додам веб-версію застосунку.

Вітаю! Ви винайшли https://www.skip.dating/

Схожості немає. Вони просять партнера відповідати на власні питання людини, а в mmeet ідея інша — людина відповідає на базові питання і вони з«єднуються в одне mmeet-відео, після спільного лайку обидва дивляться ці відео і якщо їм відкликається енергія на відео — то вони лайкають відео і домовляються про зустіч. Ці питання — це зі списку «36 питань методики психолога Артура Арона».

Добре! До речі обов’язково додайте фільни та маски як в інсті, інакше дівчата не зайдуть.

У тому то і біда, що якраз я проти цього бо коли чоловік зустрінеться з дівчиною в реальності, то буде невідповідність, та і дівчина це розуміє і тут створюється непотрібна тривога у неї і розчарування у чоловіка.
Без фільтрів може буде менше аудиторії, але вона буде справжньою налаштована на спілкування в реалі, а не онлайн.

Ви мали рацію, фільтри треба, додамо.

Ну так, то 10 років дейтинг індустрії за плечима :)

а̶ ̶з̶а̶ ̶о̶к̶р̶е̶м̶у̶ ̶п̶л̶а̶т̶у̶ ̶-̶ ̶п̶е̶р̶е̶г̶л̶я̶д̶ ̶б̶е̶з̶

До речі, зараз йде закрите тестування Android. Хто хоче спробувати:
1. Приєднатись до групи → groups.google.com/g/mmeet-testers
2. Відкрити на Android → play.google.com/...​apps/testing/app.mmeet.v2
Буду вдячний за фідбек у ЛС.

Підписатись на коментарі