Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
madhead committed Oct 24, 2023
1 parent a1e1248 commit b8bcb58
Show file tree
Hide file tree
Showing 54 changed files with 980 additions and 50 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package me.madhead.tyzenhaus.core.service

import me.madhead.tyzenhaus.entity.transaction.Transaction
import me.madhead.tyzenhaus.repository.TransactionRepository

data class TransactionsSearchParams(
val title: String? = null,
)

class TransactionsSearchService(
private val transactionRepository: TransactionRepository,
) {
fun search(group: Long, searchParams: TransactionsSearchParams): List<Transaction> {
return transactionRepository.search(group)
}
}
4 changes: 4 additions & 0 deletions launcher/fly/requests.http
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ Authorization: Bearer {{api_token}}
### Get group currencies
GET https://{{ngrok}}/app/api/group/currencies
Authorization: Bearer {{api_token}}

### Search for transactions
GET https://{{ngrok}}/app/api/group/transactions
Authorization: Bearer {{api_token}}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package me.madhead.tyzenhaus.launcher.fly.koin

import me.madhead.tyzenhaus.core.service.GroupCurrenciesService
import me.madhead.tyzenhaus.core.service.GroupMembersService
import me.madhead.tyzenhaus.core.service.TransactionsSearchService
import org.koin.dsl.module

val serviceModule = module {
Expand All @@ -17,4 +18,10 @@ val serviceModule = module {
groupConfigRepository = get(),
)
}

single {
TransactionsSearchService(
transactionRepository = get()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import io.ktor.server.routing.route
import io.ktor.utils.io.core.toByteArray
import me.madhead.tyzenhaus.core.service.GroupCurrenciesService
import me.madhead.tyzenhaus.core.service.GroupMembersService
import me.madhead.tyzenhaus.core.service.TransactionsSearchParams
import me.madhead.tyzenhaus.core.service.TransactionsSearchService
import me.madhead.tyzenhaus.launcher.fly.security.APITokenPrincipal
import org.koin.ktor.ext.get
import org.koin.ktor.ext.inject
Expand All @@ -28,6 +30,7 @@ fun Route.miniAppAPI() {
val config by inject<ApplicationConfig>()
val groupMembersService by inject<GroupMembersService>()
val groupCurrenciesService by inject<GroupCurrenciesService>()
val transactionsSearchService by inject<TransactionsSearchService>()
val webAppDataSecretKeyHash by lazy {
HMAC.hmacSHA256(
"WebAppData".toByteArray(),
Expand Down Expand Up @@ -77,6 +80,12 @@ fun Route.miniAppAPI() {

call.respond(currencies)
}

get("transactions") {
val principal = call.principal<APITokenPrincipal>()!!

call.respond(transactionsSearchService.search(principal.groupId, TransactionsSearchParams()))
}
}
}
}
Expand Down
366 changes: 366 additions & 0 deletions mini-app/.pnp.cjs

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
8 changes: 7 additions & 1 deletion mini-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@
"packageManager": "[email protected]",
"dependencies": {
"@twa-dev/sdk": "^6.9.0",
"i18next": "^23.6.0",
"i18next-browser-languagedetector": "^7.1.0",
"i18next-http-backend": "^2.2.2",
"normalize.css": "^8.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-i18next": "^13.3.1"
},
"devDependencies": {
"@types/node": "18.18.2",
"@types/react": "^18.2.28",
"@types/react-dom": "^18.2.13",
"@types/webpack": "^5.28.3",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1",
"html-webpack-plugin": "^5.5.3",
"less": "^4.2.0",
Expand Down
56 changes: 56 additions & 0 deletions mini-app/src/common/AppWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import WebApp from "@twa-dev/sdk";
import { ReactNode, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import Error from "./error/Error";

type AppWrapperProps = {
children: ReactNode;
};

export default function AppWrapper(props: AppWrapperProps) {
const { platform } = WebApp;
const { t } = useTranslation();

if (platform === "unknown") {
return <Error error={t("errors.outsideOfTelegram")} />;
} else {
return AuthWrapper(props);
}
}

type AuthWrapperProps = AppWrapperProps;

function AuthWrapper({ children }: AuthWrapperProps) {
const [ok, setOk] = useState<boolean | null>(null);

useEffect(() => {
async function authenticate() {
let response = await fetch("/app/api/auth/validation", {
method: "POST",
headers: {
Authorization: `Bearer ${WebApp.initDataUnsafe.start_param}`,
},
body: WebApp.initData,
});

setOk(response.ok);
}

authenticate();
}, [
WebApp,
WebApp.initData,
WebApp.initDataUnsafe,
WebApp.initDataUnsafe.start_param,
]);

const { t } = useTranslation();

if (ok === null) {
return null;
} else if (ok === false) {
return <Error error={t("errors.unauthorized")} />;
} else {
return children;
}
}
10 changes: 10 additions & 0 deletions mini-app/src/common/error/Error.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#error {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: red;
font-size: large;
text-align: center;
}
11 changes: 11 additions & 0 deletions mini-app/src/common/error/Error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import "./Error.less";

type ErrorProps = {
error: string;
};

function Error({ error }: ErrorProps) {
return <div id="error">{error}</div>;
}

export default Error;
84 changes: 72 additions & 12 deletions mini-app/src/history/HistoryApp.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,89 @@
import { useEffect, useState } from "react";
import "./HistoryApp.less";

import WebApp from "@twa-dev/sdk";
import { useEffect, useState } from "react";
import AppWrapper from "../common/AppWrapper";

type Member = {
id: number;
firstName: string;
lastName: string;
username: string;
};
type Currency = string;
type Transaction = {
id: number;
payer: number;
recipients: Set<number>;
// amount: BigDecimal,
currency: string;
title: string;
// @Serializable(InstantSerializer::class)
// val timestamp: Instant = Instant.now(),
};

function HistoryApp() {
console.log(WebApp.initData);
console.log(WebApp.initDataUnsafe);
const [members, setMembers] = useState<Member[]>([]);
const [currencies, setCurrencies] = useState<Currency[]>([]);
const [transactions, setTransactions] = useState<Transaction[]>([]);

useEffect(() => {
async function loadMembers() {
let response = await fetch("/app/api/group/members", {
method: "GET",
headers: {
Authorization: `Bearer ${WebApp.initDataUnsafe.start_param}`,
},
});

setMembers((await response.json()).members);
}

const [data, setData] = useState(null);
loadMembers();
}, []);
useEffect(() => {
async function loadCurrencies() {
let response = await fetch("/app/api/group/currencies", {
method: "GET",
headers: {
Authorization: `Bearer ${WebApp.initDataUnsafe.start_param}`,
},
});

setCurrencies(await response.json());
}

loadCurrencies();
}, []);
useEffect(() => {
async function fetchData() {
const data = await fetch("/app/api/test");
const json = await data.json();
setData(json);
async function loadTransactions() {
let response = await fetch("/app/api/group/transactions", {
method: "GET",
headers: {
Authorization: `Bearer ${WebApp.initDataUnsafe.start_param}`,
},
});

setTransactions(await response.json());
}

fetchData();
loadTransactions();
}, []);

return (
<div>
<AppWrapper>
<h1>History</h1>
Init Data: {WebApp.initData}
</div>
{transactions.map((transaction) => (
<Transaction {...transaction} />
))}
</AppWrapper>
);
}

function Transaction(transaction: Transaction) {
return <div className="transaction">
{transaction.title}
</div>;
}

export default HistoryApp;
15 changes: 15 additions & 0 deletions mini-app/src/history/history.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@import url(https://fonts.googleapis.com/css2?family=Roboto);
@import "~normalize.css/normalize.css";

body {
font-family: "Roboto", sans-serif;
background-color: var(--tg-theme-bg-color);
}

#root {
min-height: 100vh;
box-sizing: border-box;
padding: 24px;
display: flex;
flex-direction: column;
}
6 changes: 6 additions & 0 deletions mini-app/src/history/history.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import React from "react";
import ReactDOM from "react-dom/client";

import "@twa-dev/sdk";

import "../i18n/i18n";

import "./history.less";

import HistoryApp from "./HistoryApp";

const root = ReactDOM.createRoot(
Expand Down
6 changes: 6 additions & 0 deletions mini-app/src/i18n/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"errors": {
"outsideOfTelegram": "This app must be executed inside a Telegram client",
"unauthorized": "You are not authorized to use this app"
}
}
21 changes: 21 additions & 0 deletions mini-app/src/i18n/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import i18next from "i18next";
import { initReactI18next } from "react-i18next";
import HttpBackend from "i18next-http-backend";
import LanguageDetector from "i18next-browser-languagedetector";
import WebApp from "@twa-dev/sdk";

document.documentElement.lang = WebApp.initDataUnsafe.user?.language_code || "en";

const httpBackend = new HttpBackend(null, {
loadPath: "/app/i18n/{{lng}}.json",
});

const languageDetector = new LanguageDetector(null, {
order: ["htmlTag"],
});

i18next.use(httpBackend).use(languageDetector).use(initReactI18next).init({
fallbackLng: "en",
});

export default i18next;
Empty file added mini-app/src/i18n/it.json
Empty file.
Empty file added mini-app/src/i18n/pt.json
Empty file.
5 changes: 5 additions & 0 deletions mini-app/src/i18n/ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"errors": {
"outsideOfTelegram": "Это приложение нужно запускать внутри Telegram клиента"
}
}
1 change: 1 addition & 0 deletions mini-app/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<html lang="">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Tyzenhaus</title>
</head>
<body>
Expand Down
1 change: 1 addition & 0 deletions mini-app/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"target": "es5",
"module": "commonjs",
"esModuleInterop": true,
"sourceMap": true,
"strict": true,
"lib": ["dom", "es5"],
"jsx": "react-jsx"
Expand Down
Loading

0 comments on commit b8bcb58

Please sign in to comment.