Skip to content

Commit 8f17198

Browse files
committed
MCP related fixes
1 parent 9827ebf commit 8f17198

6 files changed

Lines changed: 182 additions & 20 deletions

File tree

.github/copilot-instructions.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Copilot Instructions for httpSMS
2+
3+
httpSMS is a service that turns an Android phone into an SMS gateway via an HTTP API. This is a monorepo with three components:
4+
5+
- **`api/`** — Go backend (Fiber, GORM, PostgreSQL)
6+
- **`web/`** — Nuxt 2 frontend (Vue 2, Vuetify 2, TypeScript)
7+
- **`android/`** — Native Android app (Kotlin)
8+
9+
## Build, Test, and Lint Commands
10+
11+
### API (Go)
12+
13+
```bash
14+
cd api
15+
16+
# Development with hot-reload
17+
air
18+
19+
# Build
20+
go build -o ./tmp/main.exe .
21+
22+
# Run tests
23+
go test ./...
24+
25+
# Run a single test
26+
go test ./pkg/services/ -run TestMessageService
27+
28+
# Generate Swagger docs (required after changing API annotations)
29+
swag init --requiredByDefault --parseDependency --parseInternal
30+
31+
# Pre-commit hooks run: go-fumpt, go-imports, go-lint, go-mod-tidy
32+
```
33+
34+
### Web (Nuxt/Vue)
35+
36+
```bash
37+
cd web
38+
39+
# Install dependencies
40+
pnpm install
41+
42+
# Development server (port 3000)
43+
pnpm dev
44+
45+
# Lint (eslint + stylelint + prettier)
46+
pnpm lint
47+
48+
# Auto-fix lint issues
49+
pnpm lintfix
50+
51+
# Run tests (Jest)
52+
pnpm test
53+
54+
# Static site generation (production build)
55+
pnpm run generate
56+
57+
# Regenerate TypeScript API models from Swagger
58+
pnpm api:models
59+
```
60+
61+
### Android (Kotlin)
62+
63+
```bash
64+
cd android
65+
66+
# Build
67+
./gradlew build
68+
69+
# Debug APK
70+
./gradlew assembleDebug
71+
72+
# Release APK
73+
./gradlew assembleRelease
74+
```
75+
76+
### Docker (full stack)
77+
78+
```bash
79+
# Start all services (PostgreSQL, Redis, API, Web)
80+
docker compose up --build
81+
# API at localhost:8000, Web at localhost:3000
82+
```
83+
84+
## Architecture
85+
86+
### API — Layered Architecture with Event-Driven Processing
87+
88+
The API uses a **DI container** (`pkg/di/container.go`) that lazily initializes all services as singletons. The layered architecture flows as:
89+
90+
**Handlers → Services → Repositories → GORM/PostgreSQL**
91+
92+
- **Handlers** (`pkg/handlers/`) — Fiber HTTP handlers. Each has a `RegisterRoutes()` method and embeds a base `handler` struct with standardized response methods (`responseBadRequest`, `responseNotFound`, etc.).
93+
- **Services** (`pkg/services/`) — Business logic. Orchestrate repositories and dispatch events.
94+
- **Repositories** (`pkg/repositories/`) — Data access via GORM. Interfaces defined alongside GORM implementations (prefixed `gorm*`).
95+
- **Validators** (`pkg/validators/`) — One validator per handler, return `url.Values` for field errors.
96+
- **Entities** (`pkg/entities/`) — Domain models, auto-migrated by GORM.
97+
98+
**Event system**: Uses CloudEvents spec (`cloudevents/sdk-go`). Events defined in `pkg/events/` (31 event types). Listeners in `pkg/listeners/` process events either synchronously or via Google Cloud Tasks queue (emulator mode for local dev).
99+
100+
**Entry point**: `main.go` loads `.env` in local mode, creates the DI container, and starts Fiber on `APP_PORT`.
101+
102+
### Web — Nuxt 2 Static SPA
103+
104+
- **State management**: Single Vuex store (`store/index.ts`) — actions make API calls via Axios, mutations update state, getters expose computed values.
105+
- **Components**: Use `vue-property-decorator` class syntax with `@Component`, `@Prop`, `@Watch` decorators.
106+
- **API client**: Axios configured in `plugins/axios.ts` with Firebase bearer token auth and `x-api-key` header support.
107+
- **API models**: TypeScript types in `models/` are auto-generated from the Swagger spec via `swagger-typescript-api`.
108+
- **Auth**: Firebase Authentication (Email/Password, Google, GitHub) with `auth` and `guest` middleware for route guards.
109+
- **Real-time**: Pusher.js for live message updates.
110+
111+
### Android — Task-Oriented, Event-Driven
112+
113+
- **No MVVM/Clean Architecture** — uses a flat package structure with Activities, Services, BroadcastReceivers, and WorkManager tasks.
114+
- **FCM integration**: `MyFirebaseMessagingService` receives push notifications → schedules `SendSmsWorker` via WorkManager → fetches message from API → sends SMS.
115+
- **Dual SIM support**: Independent settings per SIM via `Settings` singleton (SharedPreferences).
116+
- **HTTP client**: OkHttp with `x-api-key` authentication against the API.
117+
- **Encryption**: AES-256/CFB with SHA-256 key derivation (`Encrypter.kt`).
118+
119+
## Key Conventions
120+
121+
### API (Go)
122+
123+
- **Error handling**: Use `github.com/palantir/stacktrace` — wrap errors with `stacktrace.Propagate(err, "context")` or `stacktrace.PropagateWithCode()`. Never return bare errors.
124+
- **Database queries**: Always use GORM query builder with context propagation (`repository.db.WithContext(ctx)`). No raw SQL.
125+
- **Route registration**: Each handler defines `RegisterRoutes()` called from the DI container. Routes follow REST conventions under `/v1/`.
126+
- **Middleware chain**: HTTP Logger → OpenTelemetry → CORS → Request Logger → Bearer Auth → API Key Auth.
127+
- **Observability**: All layers are instrumented with OpenTelemetry (Fiber, GORM, Redis). Pass `logger` and `tracer` to constructors.
128+
- **Code formatting**: `go-fumpt` (not `gofmt`), enforced via pre-commit hooks.
129+
130+
### Web (Vue/TypeScript)
131+
132+
- **Formatting**: No semicolons, single quotes, 2-space indentation (Prettier + ESLint).
133+
- **Component style**: Class-based with `vue-property-decorator`, not Options API (though some pages use `Vue.extend()`).
134+
- **Store pattern**: Actions handle async API calls and commit mutations. Access store from components via `this.$store`.
135+
136+
### Android (Kotlin)
137+
138+
- **API calls**: Use `HttpSmsApiService` singleton (static `create()` factory). OkHttp client with `x-api-key` header.
139+
- **Background work**: Use WorkManager for tasks that must survive process death. Direct `Thread { }` for lightweight background ops.
140+
- **State**: `Settings` object (SharedPreferences singleton) for all persistent state.
141+
- **Phone number formatting**: Use `libphonenumber` for E.164 format validation.

.mcp.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"mcpServers": {
3+
"playwright": {
4+
"type": "stdio",
5+
"command": "npx",
6+
"args": [
7+
"-y",
8+
"@modelcontextprotocol/server-playwright",
9+
"--base-url",
10+
"http://localhost:3000"
11+
],
12+
"env": {
13+
"BROWSER": "chromium"
14+
}
15+
}
16+
}
17+
}

android/app/build.gradle

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def gitHash = providers.exec {
1010
}.standardOutput.asText.map { it.trim() }.getOrElse("unknown")
1111

1212
android {
13-
compileSdk 36
13+
compileSdk = 36
1414

1515
defaultConfig {
1616
applicationId "com.httpsms"
@@ -46,24 +46,24 @@ android {
4646
}
4747

4848
dependencies {
49-
implementation platform('com.google.firebase:firebase-bom:33.13.0')
49+
implementation platform('com.google.firebase:firebase-bom:34.11.0')
5050
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
5151
implementation 'com.google.firebase:firebase-analytics-ktx'
5252
implementation 'com.google.firebase:firebase-messaging-ktx'
53-
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
53+
implementation 'com.squareup.okhttp3:okhttp:5.3.2'
5454
implementation 'com.jakewharton.timber:timber:5.0.1'
5555
implementation 'androidx.preference:preference-ktx:1.2.1'
56-
implementation 'androidx.work:work-runtime-ktx:2.10.1'
57-
implementation 'androidx.core:core-ktx:1.16.0'
56+
implementation 'androidx.work:work-runtime-ktx:2.11.1'
57+
implementation 'androidx.core:core-ktx:1.18.0'
5858
implementation "androidx.cardview:cardview:1.0.0"
5959
implementation 'com.beust:klaxon:5.6'
60-
implementation 'androidx.appcompat:appcompat:1.7.0'
61-
implementation 'org.apache.commons:commons-text:1.12.0'
62-
implementation 'com.google.android.material:material:1.12.0'
60+
implementation 'androidx.appcompat:appcompat:1.7.1'
61+
implementation 'org.apache.commons:commons-text:1.15.0'
62+
implementation 'com.google.android.material:material:1.13.0'
6363
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
64-
implementation 'com.googlecode.libphonenumber:libphonenumber:9.0.4'
64+
implementation 'com.googlecode.libphonenumber:libphonenumber:9.0.26'
6565
implementation 'com.klinkerapps:android-smsmms:5.2.6'
6666
testImplementation 'junit:junit:4.13.2'
67-
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
68-
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
67+
androidTestImplementation 'androidx.test.ext:junit:1.3.0'
68+
androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0'
6969
}

android/app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<uses-permission android:name="android.permission.READ_SMS" />
1313
<uses-permission android:name="android.permission.SEND_SMS" />
1414
<uses-permission android:name="android.permission.RECEIVE_SMS" />
15+
<uses-permission android:name="android.permission.RECEIVE_MMS"/>
1516
<uses-permission android:name="android.permission.INTERNET" />
1617
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
1718
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

api/pkg/handlers/user_handler.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -307,12 +307,13 @@ func (h *UserHandler) subscriptionPayments(c *fiber.Ctx) error {
307307
// @Tags Users
308308
// @Accept json
309309
// @Produce application/pdf
310-
// @Param payload body requests.UserPaymentInvoice true "Generate subscription payment invoice parameters"
311-
// @Success 200 {file} file
312-
// @Failure 400 {object} responses.BadRequest
313-
// @Failure 401 {object} responses.Unauthorized
314-
// @Failure 422 {object} responses.UnprocessableEntity
315-
// @Failure 500 {object} responses.InternalServerError
310+
// @Param payload body requests.UserPaymentInvoice true "Generate subscription payment invoice parameters"
311+
// @Param subscriptionInvoiceID path string true "ID of the subscription invoice to generate the PDF for"
312+
// @Success 200 {file} file
313+
// @Failure 400 {object} responses.BadRequest
314+
// @Failure 401 {object} responses.Unauthorized
315+
// @Failure 422 {object} responses.UnprocessableEntity
316+
// @Failure 500 {object} responses.InternalServerError
316317
// @Router /users/subscription/invoices/{subscriptionInvoiceID} [post]
317318
func (h *UserHandler) subscriptionInvoice(c *fiber.Ctx) error {
318319
ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger)

api/pkg/requests/message_send_request.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ import (
1414
// MessageSend is the payload for sending and SMS message
1515
type MessageSend struct {
1616
request
17-
From string `json:"from" example:"+18005550199"`
18-
To string `json:"to" example:"+18005550100"`
19-
Content string `json:"content" example:"This is a sample text message"`
17+
From string `json:"from" example:"+18005550199"`
18+
To string `json:"to" example:"+18005550100"`
19+
Content string `json:"content" example:"This is a sample text message"`
20+
21+
// Attachments are optional. When you provide a list of attachments, the message will be sent out as an MMS
2022
Attachments []entities.MessageAttachment `json:"attachments" validate:"optional"`
2123

2224
// Encrypted is an optional parameter used to determine if the content is end-to-end encrypted. Make sure to set the encryption key on the httpSMS mobile app

0 commit comments

Comments
 (0)