| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 9827ebf commit 8f17198
6 files changed
| Original file line number | Diff line number | Diff 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. | ||
| Original file line number | Diff line number | Diff 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 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -10,7 +10,7 @@ def gitHash = providers.exec { | |||
| 10 | 10 | }.standardOutput.asText.map { it.trim() }.getOrElse("unknown") | |
| 11 | 11 | ||
| 12 | 12 | android { | |
| 13 | - compileSdk 36 | ||
| 13 | + compileSdk = 36 | ||
| 14 | 14 | ||
| 15 | 15 | defaultConfig { | |
| 16 | 16 | applicationId "com.httpsms" | |
@@ -46,24 +46,24 @@ android { | |||
| 46 | 46 | } | |
| 47 | 47 | ||
| 48 | 48 | dependencies { | |
| 49 | - implementation platform('com.google.firebase:firebase-bom:33.13.0') | ||
| 49 | + implementation platform('com.google.firebase:firebase-bom:34.11.0') | ||
| 50 | 50 | implementation 'com.journeyapps:zxing-android-embedded:4.3.0' | |
| 51 | 51 | implementation 'com.google.firebase:firebase-analytics-ktx' | |
| 52 | 52 | 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' | ||
| 54 | 54 | implementation 'com.jakewharton.timber:timber:5.0.1' | |
| 55 | 55 | 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' | ||
| 58 | 58 | implementation "androidx.cardview:cardview:1.0.0" | |
| 59 | 59 | 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' | ||
| 63 | 63 | 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' | ||
| 65 | 65 | implementation 'com.klinkerapps:android-smsmms:5.2.6' | |
| 66 | 66 | 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' | ||
| 69 | 69 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -12,6 +12,7 @@ | |||
| 12 | 12 | <uses-permission android:name="android.permission.READ_SMS" /> | |
| 13 | 13 | <uses-permission android:name="android.permission.SEND_SMS" /> | |
| 14 | 14 | <uses-permission android:name="android.permission.RECEIVE_SMS" /> | |
| 15 | + <uses-permission android:name="android.permission.RECEIVE_MMS"/> | ||
| 15 | 16 | <uses-permission android:name="android.permission.INTERNET" /> | |
| 16 | 17 | <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | |
| 17 | 18 | <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -307,12 +307,13 @@ func (h *UserHandler) subscriptionPayments(c *fiber.Ctx) error { | |||
| 307 | 307 | // @Tags Users | |
| 308 | 308 | // @Accept json | |
| 309 | 309 | // @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 | ||
| 316 | 317 | // @Router /users/subscription/invoices/{subscriptionInvoiceID} [post] | |
| 317 | 318 | func (h *UserHandler) subscriptionInvoice(c *fiber.Ctx) error { | |
| 318 | 319 | ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -14,9 +14,11 @@ import ( | |||
| 14 | 14 | // MessageSend is the payload for sending and SMS message | |
| 15 | 15 | type MessageSend struct { | |
| 16 | 16 | 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 | ||
| 20 | 22 | Attachments []entities.MessageAttachment `json:"attachments" validate:"optional"` | |
| 21 | 23 | ||
| 22 | 24 | // 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 | |
| Back | FazBrowse Home | New Git URL |
0 commit comments