Skip to content

Commit 8ed381c

Browse files
authored
Update token caching, token service dependencies (#2396)
* no ui caching for tokens * token handler * remove query store dependencies * remove machine comment
1 parent 6d3f15c commit 8ed381c

File tree

5 files changed

+139
-16
lines changed

5 files changed

+139
-16
lines changed

taco/cmd/token_service/main.go

Lines changed: 102 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,16 @@ import (
1111
"time"
1212

1313
"github.com/diggerhq/digger/opentaco/internal/query"
14-
"github.com/diggerhq/digger/opentaco/internal/queryfactory"
15-
"github.com/diggerhq/digger/opentaco/internal/repositories"
14+
"github.com/diggerhq/digger/opentaco/internal/query/types"
1615
"github.com/diggerhq/digger/opentaco/internal/token_service"
1716
"github.com/kelseyhightower/envconfig"
1817
"github.com/labstack/echo/v4"
1918
echomiddleware "github.com/labstack/echo/v4/middleware"
19+
"gorm.io/driver/mysql"
20+
"gorm.io/driver/postgres"
21+
"gorm.io/driver/sqlite"
22+
"gorm.io/gorm"
23+
"gorm.io/gorm/logger"
2024
)
2125

2226
// change this random number to bump version of token service: 1
@@ -33,22 +37,37 @@ func main() {
3337
log.Fatalf("Failed to process configuration: %v", err)
3438
}
3539

36-
// --- Initialize Stores ---
37-
38-
// Create the database query store using the dedicated factory.
39-
queryStore, err := queryfactory.NewQueryStore(queryCfg)
40+
log.Printf("Connecting to database backend: %s", queryCfg.Backend)
41+
42+
// Connect directly to database without using QueryStore
43+
var db *gorm.DB
44+
switch queryCfg.Backend {
45+
case "postgres":
46+
db, err = connectPostgres(queryCfg.Postgres)
47+
case "mysql":
48+
db, err = connectMySQL(queryCfg.MySQL)
49+
case "sqlite", "":
50+
db, err = connectSQLite(queryCfg.SQLite)
51+
default:
52+
log.Fatalf("Unsupported database backend: %s", queryCfg.Backend)
53+
}
54+
4055
if err != nil {
41-
log.Fatalf("Failed to initialize query backend: %v", err)
56+
log.Fatalf("Failed to connect to database: %v", err)
4257
}
43-
defer queryStore.Close()
44-
45-
log.Printf("Query backend initialized: %s", queryCfg.Backend)
58+
59+
// Ensure proper cleanup
60+
sqlDB, err := db.DB()
61+
if err != nil {
62+
log.Fatalf("Failed to get underlying sql.DB: %v", err)
63+
}
64+
defer sqlDB.Close()
4665

47-
// Get the underlying *gorm.DB from the query store
48-
db := repositories.GetDBFromQueryStore(queryStore)
49-
if db == nil {
50-
log.Fatalf("Query store does not provide GetDB method")
66+
// Auto-migrate Token table only (token service doesn't need users, orgs, units, etc.)
67+
if err := db.AutoMigrate(&types.Token{}); err != nil {
68+
log.Fatalf("Failed to migrate Token table: %v", err)
5169
}
70+
log.Println("Token table migrated successfully")
5271

5372
// Create token repository
5473
tokenRepo := token_service.NewTokenRepository(db)
@@ -95,3 +114,72 @@ func main() {
95114
log.Println("Server shutdown complete")
96115
}
97116

117+
// Database connection helpers (direct connections without QueryStore overhead)
118+
119+
func connectPostgres(cfg query.PostgresConfig) (*gorm.DB, error) {
120+
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s",
121+
cfg.Host, cfg.User, cfg.Password, cfg.DBName, cfg.Port, cfg.SSLMode)
122+
123+
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
124+
Logger: logger.Default.LogMode(logger.Silent),
125+
PrepareStmt: true,
126+
})
127+
if err != nil {
128+
return nil, fmt.Errorf("failed to connect to postgres: %w", err)
129+
}
130+
131+
// Configure connection pool
132+
sqlDB, _ := db.DB()
133+
sqlDB.SetMaxOpenConns(cfg.MaxOpenConns)
134+
sqlDB.SetMaxIdleConns(cfg.MaxIdleConns)
135+
sqlDB.SetConnMaxLifetime(cfg.ConnMaxLifetime)
136+
sqlDB.SetConnMaxIdleTime(cfg.ConnMaxIdleTime)
137+
138+
return db, nil
139+
}
140+
141+
func connectMySQL(cfg query.MySQLConfig) (*gorm.DB, error) {
142+
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
143+
cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DBName)
144+
145+
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
146+
Logger: logger.Default.LogMode(logger.Silent),
147+
PrepareStmt: true,
148+
})
149+
if err != nil {
150+
return nil, fmt.Errorf("failed to connect to mysql: %w", err)
151+
}
152+
153+
// Configure connection pool
154+
sqlDB, _ := db.DB()
155+
sqlDB.SetMaxOpenConns(cfg.MaxOpenConns)
156+
sqlDB.SetMaxIdleConns(cfg.MaxIdleConns)
157+
sqlDB.SetConnMaxLifetime(cfg.ConnMaxLifetime)
158+
sqlDB.SetConnMaxIdleTime(cfg.ConnMaxIdleTime)
159+
160+
return db, nil
161+
}
162+
163+
func connectSQLite(cfg query.SQLiteConfig) (*gorm.DB, error) {
164+
dsn := fmt.Sprintf("%s?cache=%s&_busy_timeout=%d&_journal_mode=%s&_foreign_keys=%s",
165+
cfg.Path, cfg.Cache, int(cfg.BusyTimeout.Milliseconds()),
166+
cfg.PragmaJournalMode, cfg.PragmaForeignKeys)
167+
168+
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{
169+
Logger: logger.Default.LogMode(logger.Silent),
170+
PrepareStmt: true,
171+
})
172+
if err != nil {
173+
return nil, fmt.Errorf("failed to connect to sqlite: %w", err)
174+
}
175+
176+
// Configure connection pool for SQLite
177+
sqlDB, _ := db.DB()
178+
sqlDB.SetMaxOpenConns(cfg.MaxOpenConns)
179+
sqlDB.SetMaxIdleConns(cfg.MaxIdleConns)
180+
sqlDB.SetConnMaxLifetime(cfg.ConnMaxLifetime)
181+
sqlDB.SetConnMaxIdleTime(cfg.ConnMaxIdleTime)
182+
183+
return db, nil
184+
}
185+

taco/internal/token_service/handler.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ func (h *Handler) CreateToken(c echo.Context) error {
7676
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to create token"})
7777
}
7878

79+
// Prevent caching of token creation responses
80+
c.Response().Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, private")
81+
c.Response().Header().Set("Pragma", "no-cache")
82+
c.Response().Header().Set("Expires", "0")
83+
7984
return c.JSON(http.StatusCreated, toTokenResponse(token))
8085
}
8186

@@ -95,6 +100,11 @@ func (h *Handler) ListTokens(c echo.Context) error {
95100
responses[i] = toTokenResponseHidden(token) // Hide token hash
96101
}
97102

103+
// Prevent caching of token list responses
104+
c.Response().Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, private")
105+
c.Response().Header().Set("Pragma", "no-cache")
106+
c.Response().Header().Set("Expires", "0")
107+
98108
return c.JSON(http.StatusOK, responses)
99109
}
100110

@@ -110,6 +120,11 @@ func (h *Handler) DeleteToken(c echo.Context) error {
110120
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
111121
}
112122

123+
// Prevent caching of delete responses
124+
c.Response().Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, private")
125+
c.Response().Header().Set("Pragma", "no-cache")
126+
c.Response().Header().Set("Expires", "0")
127+
113128
return c.JSON(http.StatusOK, map[string]string{"message": "Token deleted successfully"})
114129
}
115130

ui/server-start.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,22 @@ const server = createServer(async (req, res) => {
147147
});
148148

149149
// Enable HTML caching with must-revalidate for versioned builds
150+
// BUT: Disable caching for sensitive pages (settings, tokens)
150151
const contentType = response.headers.get('content-type') || '';
151152
if (contentType.includes('text/html')) {
152-
res.setHeader('Cache-Control', 'public, max-age=60, must-revalidate');
153+
const url = new URL(req.url, `http://${req.headers.host}`);
154+
const isSensitivePage = url.pathname.includes('/settings') ||
155+
url.pathname.includes('/tokens');
156+
157+
if (isSensitivePage) {
158+
// No caching for sensitive pages
159+
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, private');
160+
res.setHeader('Pragma', 'no-cache');
161+
res.setHeader('Expires', '0');
162+
} else {
163+
// Cache non-sensitive pages
164+
res.setHeader('Cache-Control', 'public, max-age=60, must-revalidate');
165+
}
153166
}
154167

155168
// Check if client accepts gzip and response is compressible

ui/src/api/tokens.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ export const getTokens = async (organizationId: string, userId: string) => {
66
method: 'GET',
77
headers: {
88
'Content-Type': 'application/json',
9+
'Cache-Control': 'no-cache',
10+
'Pragma': 'no-cache',
911
},
12+
// Disable browser caching for token requests
13+
cache: 'no-store',
1014
})
1115
if (!response.ok) {
1216
throw new Error(`Failed to get tokens: ${response.statusText}`);

ui/src/routes/_authenticated/_dashboard/dashboard/settings.tokens.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ export const Route = createFileRoute(
1818
const { user, organisationId } = context;
1919
const tokens = await getTokensFn({data: {organizationId: organisationId, userId: user?.id || ''}})
2020
return { tokens, user, organisationId }
21-
}
21+
},
22+
// Disable caching for token data - always fetch fresh
23+
staleTime: 0,
24+
gcTime: 0,
2225
})
2326

2427
function RouteComponent() {

0 commit comments

Comments
 (0)