Skip to content
This repository has been archived by the owner on Oct 10, 2024. It is now read-only.

Commit

Permalink
Add Members Transactions (#8)
Browse files Browse the repository at this point in the history
* chore!: migrate to Yarn v4

BREAKING CHANGE: modern Yarn is required since this commit

* feat: personalized fallback userpics

* fix: use single prisma instance

* feat: add member balances
  • Loading branch information
imcatwhocode authored Dec 3, 2023
1 parent 46cff8a commit d3fe2e1
Show file tree
Hide file tree
Showing 22 changed files with 5,710 additions and 3,455 deletions.
10 changes: 10 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
root = true

[*]
end_of_line = lf
insert_final_newline = true

[*.{js,json,yml}]
charset = utf-8
indent_style = space
indent_size = 2
4 changes: 3 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
* text eol=lf
* text eol=lf
/.yarn/releases/** binary
/.yarn/plugins/** binary
11 changes: 10 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,13 @@ next-env.d.ts

# configuration
.env
.idea/
.idea/

# modern yarn
.yarn/*
!.yarn/cache
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
1 change: 1 addition & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
41 changes: 14 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,21 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
# Swynca

## Getting Started
Shiny Ultra Brand New Accounting & Members Management.

First, run the development server:
README is TBD.

```bash
yarn dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.

[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.

The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
## Installation

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
This is Next.js project with modern Yarn as package manager.
Current Node.js LTS release is required.

## Learn More
```shell
# If you don't have Yarn v2 installed, run:
corepack enable
yarn set version stable

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
# Install deps
yarn install
```

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
**TODO:** configuration, SSO, all the stuff. Currently, reach to @imcatwhocode or @shatie for instructions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,6 @@
"pretty-quick": "^3.1.3",
"prisma": "^5.5.2",
"tailwindcss": "^3.3.5"
}
},
"packageManager": "[email protected]"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
-- CreateEnum
CREATE TYPE "MemberTransactionDeposit" AS ENUM ('MAGIC', 'DONATE', 'TOPUP');

-- CreateEnum
CREATE TYPE "MemberTransactionWithdrawal" AS ENUM ('MAGIC', 'MEMBERSHIP');

-- CreateTable
CREATE TABLE "MemberTransaction" (
"id" TEXT NOT NULL,
"type" "TransactionType" NOT NULL,
"amount" DECIMAL(18,2) NOT NULL,
"comment" TEXT,
"date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"subjectId" TEXT NOT NULL,
"actorId" TEXT,
"source" "MemberTransactionDeposit",
"target" "MemberTransactionWithdrawal",
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deleted" TIMESTAMP(3),

CONSTRAINT "MemberTransaction_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "MemberTransaction" ADD CONSTRAINT "MemberTransaction_actorId_fkey" FOREIGN KEY ("actorId") REFERENCES "Member"("id") ON DELETE SET NULL ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "MemberTransaction" ADD CONSTRAINT "MemberTransaction_subjectId_fkey" FOREIGN KEY ("subjectId") REFERENCES "Member"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
59 changes: 54 additions & 5 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "linux-musl-arm64-openssl-3.0.x"]
provider = "prisma-client-js"
binaryTargets = ["native", "linux-musl-arm64-openssl-3.0.x"]
}

datasource db {
Expand Down Expand Up @@ -48,6 +48,25 @@ enum SpaceTransactionWithdrawal {
PURCHASES
}

enum MemberTransactionDeposit {
// Magically appears. For data migraiton and corrections.
MAGIC
// Donation (doesn't affect balance)
DONATE
// Top-up own balance
TOPUP
}

enum MemberTransactionWithdrawal {
// Magically disappears. For data migraiton and corrections.
MAGIC
// Recurring membership fee
MEMBERSHIP
}

model Member {
id String @id @default(uuid())
name String
Expand All @@ -56,10 +75,13 @@ model Member {
status MemberStatuses @default(ACTIVE)
joinedAt DateTime @default(now())
ExternalAuthenticationLogto ExternalAuthenticationLogto?
SpaceTransaction SpaceTransaction[]
ACSKey ACSKey[]
ExternalAuthenticationLogto ExternalAuthenticationLogto?
MembershipSubscriptionHistory MembershipSubscription[]
SpaceTransaction SpaceTransaction[]
ActoredTransactions MemberTransaction[] @relation("ActoredTransactions")
SubjectedTransactions MemberTransaction[] @relation("SubjectedTransactions")
}

model ACSKey {
Expand All @@ -74,7 +96,7 @@ model ACSKey {
}

model ExternalAuthenticationLogto {
logtoId String @id @unique
logtoId String @id @unique
memberId String @unique
member Member @relation(fields: [memberId], references: [id])
}
Expand Down Expand Up @@ -107,6 +129,33 @@ model SpaceTransaction {
Actor Member? @relation(fields: [actorId], references: [id])
}

model MemberTransaction {
id String @id @default(uuid())
type TransactionType
amount Decimal @db.Decimal(18, 2)
comment String?
date DateTime @default(now())
// Subject of this transaction
subjectId String
// Who created this transaction. Null if created automatically.
actorId String?
// For Deposit transactions, source of funds
source MemberTransactionDeposit?
// For Withdrawal transactions, funds target
target MemberTransactionWithdrawal?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deleted DateTime?
Actor Member? @relation(fields: [actorId], references: [id], name: "ActoredTransactions")
Subject Member? @relation(fields: [subjectId], references: [id], name: "SubjectedTransactions")
}

model Membership {
id String @id @unique @default(uuid())
title String
Expand Down
11 changes: 8 additions & 3 deletions src/app/(protected)/_components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,20 @@ export default function Header() {
<span className="sr-only">Open user menu</span>
{!!session?.user?.image ? (
<Image
className="w-8 h-8 rounded-xl"
className="w-8 h-8 rounded-full"
src={session?.user?.image as string}
width={32}
height={32}
alt="user photo"
/>
) : (
<div className="w-8 h-8 bg-slate-300 rounded-xl flex justify-center items-center">
?
<div className="w-8 h-8 bg-gradient-to-tr from-purple-500 to-pink-500 text-white rounded-full flex justify-center items-center">
<span className="text-xs font-semibold" aria-hidden>
{session?.user.name
?.split(" ")
.map((n) => n[0])
.join("")}
</span>
</div>
)}
</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use server";

import { getSession } from "@/app/auth";
import { create } from "@/lib/space-transactions";
import { create } from "@/lib/space-transaction";
import { Prisma, TransactionType } from "@prisma/client";
import { revalidateTag } from "next/cache";
import { Literal, Record, Static, String, Union } from "runtypes";
Expand Down
2 changes: 1 addition & 1 deletion src/app/(protected)/finance/space/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import classNames from "classnames";
import { getBalance, getBasicExpenses } from "@/lib/space-transactions";
import { getBalance, getBasicExpenses } from "@/lib/space-transaction";
import { formatCurrency } from "@/lib/locale";
import CreateTransactionButton from "../_components/create-transaction/button";
import Pagination from "./pagination";
Expand Down
6 changes: 4 additions & 2 deletions src/app/(protected)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ export default async function DashboardPage() {

return (
<div>
<h1>Look ma, rendered on server</h1>
<pre>{JSON.stringify(session.user, null, 2)}</pre>
<h1 className="text-6xl italic leading-[120%] font-bold">
If only I could hug you,
<br /> but I&apos;m just a text.
</h1>
</div>
);
}
5 changes: 2 additions & 3 deletions src/lib/acs/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { PrismaClient, KeyType, ACSKey } from "@prisma/client";
import { KeyType, ACSKey } from "@prisma/client";
import { isACSUID, isPAN } from "../validation";

const prisma = new PrismaClient();
import prisma from "../db";

export async function getMemberKeys(memberId: string) {
return prisma.aCSKey.findMany({
Expand Down
17 changes: 17 additions & 0 deletions src/lib/db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { PrismaClient } from "@prisma/client";

const prismaClientSingleton = () => {
return new PrismaClient();
};

type PrismaClientSingleton = ReturnType<typeof prismaClientSingleton>;

const globalForPrisma = globalThis as unknown as {
prisma: PrismaClientSingleton | undefined;
};

const prisma = globalForPrisma.prisma ?? prismaClientSingleton();

export default prisma;

if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
6 changes: 3 additions & 3 deletions src/lib/integrations/logto/account-management.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import prisma from "@/lib/db";
import { getRequiredEnv } from "@/lib/utils/env";
import { PrismaClient } from "@prisma/client";
import axios, { AxiosInstance } from "axios";
Expand Down Expand Up @@ -102,11 +103,10 @@ export default class LogtoAccountManagement
this.appId = getRequiredEnv("LOGTO_M2M_APP_ID");
this.appSecret = getRequiredEnv("LOGTO_M2M_APP_SECRET");
this.baseUrl = getRequiredEnv("LOGTO_M2M_ENDPOINT");
this.prisma = new PrismaClient();
}

async bind(memberId: string, externalId: string): Promise<void> {
await this.prisma.externalAuthenticationLogto.upsert({
await prisma.externalAuthenticationLogto.upsert({
where: {
logtoId: externalId,
},
Expand All @@ -119,7 +119,7 @@ export default class LogtoAccountManagement
}

async getExternalId(memberId: string): Promise<string | null> {
const binding = await this.prisma.externalAuthenticationLogto.findUnique({
const binding = await prisma.externalAuthenticationLogto.findUnique({
where: {
memberId,
},
Expand Down
4 changes: 1 addition & 3 deletions src/lib/integrations/logto/auth-provider.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { PrismaClient } from "@prisma/client";
import { OIDCConfig } from "next-auth/providers";
import { memberToProfile } from "@/lib/auth/profile";
import { getRequiredEnv } from "@/lib/utils/env";

const prisma = new PrismaClient();
import prisma from "@/lib/db";

type LogtoProfile = {
sub: string;
Expand Down
Loading

0 comments on commit d3fe2e1

Please sign in to comment.