Skip to content

Commit

Permalink
Add user transaction history (#4)
Browse files Browse the repository at this point in the history
* Added transaction history

* Changed to cum_sum calc in SQL

* Added limit of transactions

* Changed formatting of table

* Fixed/undid async and typo errors

* Remove cli-table

* Fix PR comments

* Remov command interface

* Fix formatting
  • Loading branch information
christiansegercrantz authored Mar 15, 2024
1 parent 817f887 commit 321d8ee
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 11 deletions.
3 changes: 2 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"esbenp.prettier-vscode",
"ms-azuretools.vscode-docker",
"eamodio.gitlens",
"ms-azuretools.vscode-docker"
"ms-azuretools.vscode-docker",
"qufiwefefwoyn.inline-sql-syntax"
]
},
"settings": {
Expand Down
111 changes: 106 additions & 5 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { Context, Telegraf } from 'telegraf'
import { config } from './config.js'
import {
exportTransactions,
exportTransactionsForOneUser,
getBalanceForMember,
purchaseItemForMember,
} from './transactions.js'
import { Message, Update } from '@telegraf/types'
import { QueryResult } from 'pg'

/*
Toiveiden tynnyri:
Expand Down Expand Up @@ -71,7 +73,12 @@ const addPurchaseOption = (itemDescription: string, itemPriceCents: string) => {
}
}
}
const commands = [

const products: {
command: string
description: string
priceCents: string
}[] = [
{
command: 'patron',
description: 'Patron',
Expand All @@ -98,7 +105,7 @@ const commands = [
priceCents: '-200',
},
]
commands.forEach(({ command, description, priceCents }) => {
products.forEach(({ command, description, priceCents }) => {
bot.command(command, addPurchaseOption(description, priceCents))
})

Expand All @@ -115,11 +122,69 @@ bot.command('start', async (ctx) => {
return ctx.reply(info_message)
})

bot.command('historia', async (ctx) => {
const history = await exportTransactionsForOneUser(ctx.from.id)

const parsedHistory = history.rows.map(
({ created_at, description, amount_cents }) => {
return {
created_at,
description,
amount_cents,
}
}
)
const saldo = await getBalanceForMember(ctx.from.id)
var res = `Ditt nuvarande saldo är ${saldo}. Här är din historia:\`\`\``
parsedHistory.forEach((row) => {
res +=
`\n${formatDateToString(
row.created_at
)} ${row.created_at.toLocaleTimeString('sv-fi')}, ` +
`${centsToEuroString(-row.amount_cents)}, ` +
`${row.description}`
})
res += '```'
return ctx.reply(res, { parse_mode: 'Markdown' })
})

bot.command('historia_all', async (ctx) => {
if (!(await isAdminUser(ctx))) {
return ctx.reply('Nå huhhu, håll dig ti ditt egna dåkande!')
}
const history = await exportTransactions()

const parsedHistory = history.rows.map(
({ user_name, created_at, description, amount_cents }) => {
return {
user_name,
created_at,
description,
amount_cents,
}
}
)

var res = `\`\`\``
parsedHistory.forEach((row) => {
res +=
`\n${row.user_name.split(' ').slice(0, -1).join(' ')}, ` +
`${formatDateToString(
row.created_at
)} ${row.created_at.toLocaleTimeString('sv-fi')}, ` +
`${centsToEuroString(-row.amount_cents)}, ` +
`${row.description}`
})
res += '```'
return ctx.reply(res, { parse_mode: 'Markdown' })
})

bot.command('exportera', async (ctx) => {
if (!(await isChatMember(ctx.from.id, config.adminChatId))) {
if (!(await isAdminUser(ctx))) {
return ctx.reply('sii dej i reven, pleb!')
}
const res = await exportTransactions()
// Temp solution until I or Bäck fixes the error this produces
const res: QueryResult<any> = await exportTransactions()
const headers = res.fields.map((field) => field.name)
const rows = res.rows.map((row) => {
return headers
Expand All @@ -137,14 +202,15 @@ bot.command('exportera', async (ctx) => {
})

bot.telegram.setMyCommands([
...commands.map(({ command, description, priceCents }) => ({
...products.map(({ command, description, priceCents }) => ({
command,
description: `Köp 1 st ${description} för ${(
Number(priceCents) / -100
).toFixed(2)}€`,
})),
{ command: 'saldo', description: 'Kontrollera saldo' },
{ command: 'info', description: 'Visar information om bottens användning' },
{ command: 'historia', description: 'Se din egna transaktionshistorik' },
])

bot.launch()
Expand Down Expand Up @@ -177,3 +243,38 @@ const formatName = ({
const formattedUserName = username ? ` (${username})` : ``
return `${first_name}${formattedLastName}${formattedUserName}`
}
/**
* Formats from number of cent to a string in euro. I.e. -350 becomes "-3.5€"
* @param amountInCents
* @returns
*/
function centsToEuroString(amountInCents: number): string {
var euro = (amountInCents / -100).toFixed(2).toString()
if (euro[0] !== '-') {
euro = ' ' + euro
}
return euro + '€'
}

/**
* Checks if the user is in the admin chat
* @param ctx
* @returns
*/
const isAdminUser = async (
ctx: Context<{
message: Update.New & Update.NonChannel & Message.TextMessage
update_id: number
}> &
Omit<Context<Update>, keyof Context<Update>>
) => {
const res: Promise<boolean> = isChatMember(ctx.from.id, config.adminChatId)
return res
}
function formatDateToString(date: Date) {
return `${date.toLocaleDateString('sv-fi', {
year: '2-digit',
month: '2-digit',
day: '2-digit',
})} ${date.toLocaleDateString('sv-fi', { weekday: 'short' })}`
}
33 changes: 30 additions & 3 deletions backend/src/transactions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import { z } from 'zod'
import { pool } from './db.js'
import { QueryResult } from 'pg'

interface Transaction {
export interface Transaction {
id: number
created_at: Date
userId: number
user_name: string
description: string
amount_cents: number
}

export interface TransactionInsert {
userId: number
userName: string
description: string
Expand All @@ -13,7 +23,7 @@ export const purchaseItemForMember = async ({
userName,
description,
amountCents,
}: Transaction) => {
}: TransactionInsert) => {
await pool.query(
`INSERT INTO transactions(
user_id,
Expand Down Expand Up @@ -42,11 +52,28 @@ export const getBalanceForMember = async (userId: number) => {
}
}

export const exportTransactions = async () => {
export const exportTransactions = async (): Promise<
QueryResult<Transaction>
> => {
const res = await pool.query(`SELECT * FROM transactions`)
return res
}

export const exportTransactionsForOneUser = async (
userId: number
): Promise<QueryResult<Transaction>> => {
const res = await pool.query(
`--sql
SELECT *
FROM transactions
WHERE user_id = $1
ORDER BY id DESC
LIMIT 30`,
[userId]
)
return res
}

const BalanceResponseSchema = z
.array(
z.object({
Expand Down
50 changes: 48 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 321d8ee

Please sign in to comment.