Some checks failed
Backend Tests / Static Checks (push) Has been cancelled
Backend Tests / Tests (other) (push) Has been cancelled
Backend Tests / Tests (plugin) (push) Has been cancelled
Backend Tests / Tests (server) (push) Has been cancelled
Backend Tests / Tests (store) (push) Has been cancelled
Build Canary Image / build-frontend (push) Has been cancelled
Build Canary Image / build-push (linux/amd64) (push) Has been cancelled
Build Canary Image / build-push (linux/arm64) (push) Has been cancelled
Build Canary Image / merge (push) Has been cancelled
Frontend Tests / Lint (push) Has been cancelled
Frontend Tests / Build (push) Has been cancelled
Proto Linter / Lint Protos (push) Has been cancelled
379 lines
9.6 KiB
Plaintext
379 lines
9.6 KiB
Plaintext
# MEMOS - PROJECT INFO (Bahasa Indonesia)
|
|
|
|
## 📋 STRUKTUR PROYEK
|
|
|
|
### Arsitektur Utama
|
|
Proyek Memos adalah aplikasi catatan pribadi berbasis web yang dibangun dengan:
|
|
- **Backend**: Go (Golang) versi 1.25
|
|
- **Frontend**: React 18.3 + TypeScript
|
|
- **Protokol API**: gRPC + Connect RPC
|
|
- **Database**: SQLite (default), MySQL, PostgreSQL
|
|
|
|
### Struktur Direktori Utama
|
|
|
|
```
|
|
memos/
|
|
├── cmd/memos/ # Entry point aplikasi
|
|
│ └── main.go # File utama untuk menjalankan server
|
|
├── server/ # Server HTTP dan routing
|
|
│ ├── server.go # Konfigurasi Echo server
|
|
│ ├── router/ # Routing API dan frontend
|
|
│ │ ├── api/v1/ # Implementasi service gRPC
|
|
│ │ ├── frontend/ # Serving file statis React
|
|
│ │ └── fileserver/ # Serving file media
|
|
│ └── auth/ # Autentikasi (JWT, PAT)
|
|
├── store/ # Layer data dan database
|
|
│ ├── db/ # Driver database (sqlite/mysql/postgres)
|
|
│ ├── migration/ # File migrasi database
|
|
│ └── *.go # Model dan operasi data
|
|
├── proto/ # Definisi Protocol Buffer
|
|
│ ├── api/v1/ # Service dan message definitions
|
|
│ └── gen/ # Kode yang di-generate
|
|
├── web/ # Aplikasi frontend React
|
|
│ ├── src/ # Source code TypeScript
|
|
│ ├── package.json # Dependencies frontend
|
|
│ └── vite.config.mts # Konfigurasi Vite
|
|
└── plugin/ # Plugin tambahan
|
|
├── markdown/ # Parsing markdown
|
|
├── email/ # Pengiriman email
|
|
├── scheduler/ # Cron jobs
|
|
└── webhook/ # Webhook integration
|
|
```
|
|
|
|
## 🔧 CARA PEMBUATAN API
|
|
|
|
### 1. Definisi Protokol Buffer (.proto)
|
|
|
|
API didefinisikan dalam file `.proto` di direktori `proto/api/v1/`:
|
|
|
|
Contoh: `memo_service.proto`
|
|
```protobuf
|
|
syntax = "proto3";
|
|
|
|
package memos.api.v1;
|
|
|
|
service MemoService {
|
|
rpc CreateMemo(CreateMemoRequest) returns (Memo) {
|
|
option (google.api.http) = {
|
|
post: "/api/v1/memos"
|
|
body: "memo"
|
|
};
|
|
}
|
|
|
|
rpc ListMemos(ListMemosRequest) returns (ListMemosResponse) {
|
|
option (google.api.http) = {get: "/api/v1/memos"};
|
|
}
|
|
}
|
|
|
|
message Memo {
|
|
string name = 1;
|
|
string content = 7 [(google.api.field_behavior) = REQUIRED];
|
|
Visibility visibility = 9 [(google.api.field_behavior) = REQUIRED];
|
|
}
|
|
```
|
|
|
|
### 2. Generate Kode dari Proto
|
|
|
|
Jalankan perintah:
|
|
```bash
|
|
cd proto && buf generate
|
|
```
|
|
|
|
Ini akan menghasilkan:
|
|
- Kode Go di `proto/gen/api/v1/` (untuk backend)
|
|
- Kode TypeScript di `web/src/types/proto/api/v1/` (untuk frontend)
|
|
|
|
### 3. Implementasi Service Backend
|
|
|
|
File: `server/router/api/v1/memo_service.go`
|
|
|
|
```go
|
|
func (s *APIV1Service) CreateMemo(ctx context.Context, request *v1pb.CreateMemoRequest) (*v1pb.Memo, error) {
|
|
// 1. Validasi user
|
|
user, err := s.fetchCurrentUser(ctx)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Unauthenticated, "user not authenticated")
|
|
}
|
|
|
|
// 2. Mapping ke model store
|
|
create := &store.Memo{
|
|
UID: shortuuid.New(),
|
|
CreatorID: user.ID,
|
|
Content: request.Memo.Content,
|
|
Visibility: convertVisibilityToStore(request.Memo.Visibility),
|
|
}
|
|
|
|
// 3. Simpan ke database
|
|
memo, err := s.Store.CreateMemo(ctx, create)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 4. Convert ke response proto
|
|
return s.convertMemoFromStore(ctx, memo, nil, nil)
|
|
}
|
|
```
|
|
|
|
### 4. Registrasi Service
|
|
|
|
File: `server/router/api/v1/v1.go`
|
|
|
|
```go
|
|
// Register gRPC gateway
|
|
if err := apiV1Service.RegisterGateway(ctx, echoServer); err != nil {
|
|
return nil, errors.Wrap(err, "failed to register gRPC gateway")
|
|
}
|
|
|
|
// Register Connect handlers
|
|
connectHandlers := v1pbconnect.NewMemoServiceHandler(apiV1Service)
|
|
echoServer.POST("/memos.api.v1.MemoService/CreateMemo",
|
|
connect.NewHandler(connectHandlers.CreateMemo))
|
|
```
|
|
|
|
### 5. Middleware dan Interceptor
|
|
|
|
Autentikasi dilakukan melalui interceptor:
|
|
- **Connect Interceptor**: `connect_interceptors.go`
|
|
- **gRPC Gateway Interceptor**: Middleware Echo
|
|
|
|
## 🖥️ FRONTEND MENGGUNAKAN TEKNOLOGI
|
|
|
|
### Framework dan Library Utama
|
|
|
|
1. **React 18.3** - Library UI utama
|
|
2. **TypeScript** - Typing untuk JavaScript
|
|
3. **Vite 7** - Build tool dan development server
|
|
4. **Tailwind CSS 4** - Styling utility-first
|
|
5. **React Query (TanStack Query) v5** - State management server
|
|
6. **Connect RPC** - Komunikasi dengan backend
|
|
|
|
### State Management
|
|
|
|
#### 1. Server State (React Query)
|
|
```typescript
|
|
// hooks/useMemoQueries.ts
|
|
export const useMemos = (filters: MemoFilters) => {
|
|
return useQuery({
|
|
queryKey: memoKeys.list(filters),
|
|
queryFn: () => memoServiceClient.listMemos({ filter: filters.query }),
|
|
});
|
|
};
|
|
|
|
export const useCreateMemo = () => {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (memo: CreateMemoRequest) =>
|
|
memoServiceClient.createMemo(memo),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: memoKeys.all });
|
|
},
|
|
});
|
|
};
|
|
```
|
|
|
|
#### 2. Client State (React Context)
|
|
```typescript
|
|
// contexts/AuthContext.tsx
|
|
const AuthContext = createContext<AuthContextValue | null>(null);
|
|
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
const [currentUser, setCurrentUser] = useState<User | undefined>();
|
|
|
|
const logout = useCallback(async () => {
|
|
await authServiceClient.signOut({});
|
|
setCurrentUser(undefined);
|
|
}, []);
|
|
|
|
return (
|
|
<AuthContext.Provider value={{ currentUser, logout }}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Komunikasi API
|
|
|
|
File: `web/src/connect.ts`
|
|
|
|
```typescript
|
|
// Transport dengan autentikasi
|
|
const transport = createConnectTransport({
|
|
baseUrl: window.location.origin,
|
|
useBinaryFormat: true,
|
|
interceptors: [authInterceptor], // Menangani JWT token
|
|
});
|
|
|
|
// Client service
|
|
export const memoServiceClient = createClient(MemoService, transport);
|
|
export const userServiceClient = createClient(UserService, transport);
|
|
```
|
|
|
|
### Routing
|
|
|
|
Menggunakan React Router DOM v7:
|
|
```typescript
|
|
// router/index.tsx
|
|
const router = createBrowserRouter([
|
|
{
|
|
path: "/",
|
|
element: <App />,
|
|
children: [
|
|
{ index: true, element: <Home /> },
|
|
{ path: "/m/:memoId", element: <MemoDetail /> },
|
|
{ path: "/auth", element: <AuthLayout /> },
|
|
],
|
|
},
|
|
]);
|
|
```
|
|
|
|
### Styling
|
|
|
|
Menggunakan Tailwind CSS dengan komponen UI:
|
|
```tsx
|
|
// components/ui/button.tsx
|
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
|
|
const buttonVariants = cva(
|
|
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
|
|
{
|
|
variants: {
|
|
variant: {
|
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
destructive: "bg-destructive text-destructive-foreground",
|
|
outline: "border border-input bg-background hover:bg-accent",
|
|
},
|
|
size: {
|
|
default: "h-10 px-4 py-2",
|
|
sm: "h-9 rounded-md px-3",
|
|
lg: "h-11 rounded-md px-8",
|
|
},
|
|
},
|
|
defaultVariants: {
|
|
variant: "default",
|
|
size: "default",
|
|
},
|
|
}
|
|
);
|
|
```
|
|
|
|
## ⚙️ ARSITEKTUR API DUAL PROTOCOL
|
|
|
|
### 1. Connect RPC (Untuk Browser)
|
|
- Path: `/memos.api.v1.MemoService/*`
|
|
- Format: Binary protocol buffers
|
|
- Digunakan oleh frontend React
|
|
|
|
### 2. gRPC-Gateway (REST API)
|
|
- Path: `/api/v1/memos`
|
|
- Format: JSON over HTTP
|
|
- Berguna untuk integrasi eksternal
|
|
|
|
Keduanya menggunakan implementasi service yang sama, sehingga konsisten.
|
|
|
|
## 🔐 AUTENTIKASI
|
|
|
|
### Metode Token
|
|
1. **JWT Access Token V2** - Stateless, 15 menit expiry
|
|
2. **Personal Access Token (PAT)** - Stateful, long-lived
|
|
|
|
### Flow Autentikasi
|
|
```typescript
|
|
// Frontend interceptor
|
|
const authInterceptor: Interceptor = (next) => async (req) => {
|
|
const token = getAccessToken();
|
|
if (token) {
|
|
req.header.set("Authorization", `Bearer ${token}`);
|
|
}
|
|
|
|
try {
|
|
return await next(req);
|
|
} catch (error) {
|
|
if (error.code === Code.Unauthenticated) {
|
|
// Refresh token otomatis
|
|
await refreshAccessToken();
|
|
// Retry request
|
|
return await next(req);
|
|
}
|
|
throw error;
|
|
}
|
|
};
|
|
```
|
|
|
|
## 🗃️ DATABASE
|
|
|
|
### Layer Abstraksi
|
|
```go
|
|
type Driver interface {
|
|
CreateMemo(ctx context.Context, create *Memo) (*Memo, error)
|
|
ListMemos(ctx context.Context, find *FindMemo) ([]*Memo, error)
|
|
UpdateMemo(ctx context.Context, update *UpdateMemo) error
|
|
DeleteMemo(ctx context.Context, delete *DeleteMemo) error
|
|
}
|
|
```
|
|
|
|
### Implementasi:
|
|
- SQLite: `store/db/sqlite/`
|
|
- MySQL: `store/db/mysql/`
|
|
- PostgreSQL: `store/db/postgres/`
|
|
|
|
### Caching:
|
|
Menggunakan in-memory cache untuk:
|
|
- Pengaturan instance
|
|
- Data user
|
|
- Pengaturan user
|
|
|
|
## 🚀 DEVELOPMENT WORKFLOW
|
|
|
|
### Backend Development
|
|
```bash
|
|
# Jalankan server development
|
|
go run ./cmd/memos --port 8081
|
|
|
|
# Testing
|
|
go test ./...
|
|
|
|
# Linting
|
|
golangci-lint run
|
|
```
|
|
|
|
### Frontend Development
|
|
```bash
|
|
cd web
|
|
pnpm install
|
|
pnpm dev # Server jalan di port 3001, proxy ke 8081
|
|
```
|
|
|
|
### Generate Proto
|
|
```bash
|
|
cd proto
|
|
buf generate
|
|
```
|
|
|
|
## 📦 DEPLOYMENT
|
|
|
|
### Opsi Deployment:
|
|
1. **Docker** (Direkomendasikan)
|
|
2. **Binary langsung**
|
|
3. **Docker Compose**
|
|
4. **Kubernetes**
|
|
|
|
### Contoh Docker:
|
|
```bash
|
|
docker run -d \
|
|
--name memos \
|
|
-p 5230:5230 \
|
|
-v ~/.memos:/var/opt/memos \
|
|
neosmemo/memos:stable
|
|
```
|
|
|
|
---
|
|
|
|
**Ringkasan Teknologi Utama:**
|
|
- **Bahasa**: Go + TypeScript
|
|
- **Framework**: React 18 + Echo (Go)
|
|
- **API**: gRPC + Connect RPC + REST
|
|
- **Database**: SQLite/MySQL/PostgreSQL
|
|
- **Build Tool**: Vite + buf (proto)
|
|
- **State Management**: React Query + Context
|
|
- **Styling**: Tailwind CSS |