@plutocms/supabase 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/.prettierignore +2 -0
  2. package/CHANGELOG.md +8 -0
  3. package/FEATURES.md +6 -0
  4. package/app/components/EnvPersistWarning.vue +30 -13
  5. package/app/components/SupabaseMessageParts.vue +29 -0
  6. package/app/components/migrations/MigrationsBanner.vue +3 -4
  7. package/app/components/navbar/NavbarAdmin.vue +15 -9
  8. package/app/components/settings/SettingsGeneralPanel.vue +21 -6
  9. package/app/composables/auth.ts +17 -14
  10. package/app/pages/admin/(auth)/confirm.vue +6 -5
  11. package/app/pages/admin/(auth)/forgot-password.vue +15 -11
  12. package/app/pages/admin/(auth)/login.vue +10 -8
  13. package/app/pages/admin/(auth)/signup.vue +30 -22
  14. package/app/pages/admin/(auth)/update-password.vue +15 -12
  15. package/app/pages/admin/migrations.vue +87 -51
  16. package/app/pages/admin/setup.vue +88 -74
  17. package/app/pages/admin.vue +3 -2
  18. package/app/plugins/pluto-extension.ts +24 -3
  19. package/app/plugins/pluto-locale-sync.client.ts +30 -0
  20. package/app/utils/auth-error.ts +53 -0
  21. package/db/migrations/005_profile_locale.sql +44 -0
  22. package/i18n/locales/en.json +209 -0
  23. package/i18n/locales/pt-BR.json +209 -0
  24. package/package.json +3 -3
  25. package/server/api/profile/locale.get.ts +39 -0
  26. package/server/api/profile/locale.post.ts +71 -0
  27. package/server/api/setup/create.post.ts +6 -4
  28. package/server/api/users/[id].get.ts +4 -2
  29. package/server/api/users/index.get.ts +4 -2
  30. package/server/utils/admin-guard.ts +10 -2
  31. package/server/utils/capability-guard.ts +18 -4
  32. package/server/utils/content-adapter.ts +6 -4
  33. package/server/utils/pending-migrations.ts +0 -0
  34. package/server/utils/validate.ts +6 -1
  35. package/shared/types/supabase.ts +7 -0
@@ -0,0 +1,209 @@
1
+ {
2
+ "supabase": {
3
+ "profile": {
4
+ "migrationPending": "Apply the pending database migrations to save your language."
5
+ },
6
+ "errors": {
7
+ "notLoggedIn": "You must be logged in.",
8
+ "notAdmin": "Your account is not an admin.",
9
+ "missingPermission": "Your account is missing the \"{capability}\" permission.",
10
+ "setupCompleted": "Setup has already been completed.",
11
+ "coreSchemaMissing": "Core schema not found. Reinstall @plutocms/supabase and restart the dev server.",
12
+ "fetchUsers": "Error fetching users: {message}",
13
+ "fetchUser": "Error fetching user: {message}",
14
+ "localeUnsupported": "This language is not supported.",
15
+ "duplicateRecord": "A record with this value already exists.",
16
+ "missingReference": "This references a record that does not exist.",
17
+ "migrationStatus": "Error reading migration status: {message}"
18
+ },
19
+ "auth": {
20
+ "backToLogin": "Back to Login",
21
+ "passwordMismatch": "Passwords do not match.",
22
+ "fields": {
23
+ "email": "Email",
24
+ "password": "Password",
25
+ "username": "Username",
26
+ "displayName": "Display Name",
27
+ "confirmPassword": "Confirm Password"
28
+ },
29
+ "toasts": {
30
+ "loginFailed": "Could not log in",
31
+ "loggedOut": "Logged out successfully",
32
+ "resetFailed": "Could not send reset email",
33
+ "updateFailed": "Could not update password",
34
+ "passwordUpdated": "Password updated successfully",
35
+ "notLoggedIn": "You are not logged in",
36
+ "notLoggedInDescription": "Please log in to continue."
37
+ },
38
+ "errors": {
39
+ "unknown": "An unknown error occurred.",
40
+ "invalid_credentials": "Invalid email or password.",
41
+ "email_not_confirmed": "Your email is not confirmed yet. Check your inbox for the verification link.",
42
+ "over_request_rate_limit": "Too many attempts. Wait a moment and try again.",
43
+ "over_email_send_rate_limit": "Too many emails sent. Wait a moment and try again.",
44
+ "weak_password": "This password is too weak. Choose a stronger one.",
45
+ "same_password": "The new password must be different from the current one.",
46
+ "user_not_found": "No account was found for this email.",
47
+ "email_exists": "An account with this email already exists.",
48
+ "user_already_exists": "An account with this email already exists.",
49
+ "signup_disabled": "Sign up is turned off for this site.",
50
+ "email_address_invalid": "This email address is not valid."
51
+ },
52
+ "confirm": {
53
+ "pageTitle": "Account Verification",
54
+ "title": "Account verified!",
55
+ "description": "Your account has been successfully verified. You can now start using the Dashboard.",
56
+ "goToDashboard": "Go to Dashboard"
57
+ },
58
+ "forgot": {
59
+ "pageTitle": "Forgot Password",
60
+ "title": "Forgot Password",
61
+ "description": "Enter your email and we'll send you a reset link.",
62
+ "submit": "Send Reset Link",
63
+ "sentTitle": "Check your inbox",
64
+ "sentDescription": "If an account exists for {email}, a password reset link has been sent. Check your email and follow the link."
65
+ },
66
+ "login": {
67
+ "title": "Login",
68
+ "submit": "Login",
69
+ "createAccount": "Create Account",
70
+ "backToHome": "Back to Home",
71
+ "forgotPassword": "Forgot password?"
72
+ },
73
+ "update": {
74
+ "title": "Update Password",
75
+ "description": "Enter your new password below.",
76
+ "newPassword": "New Password",
77
+ "confirmNewPassword": "Confirm New Password",
78
+ "submit": "Update Password",
79
+ "doneTitle": "Password Updated!",
80
+ "doneDescription": "Your password has been changed. You can now log in with your new password.",
81
+ "goToLogin": "Go to Login"
82
+ },
83
+ "signup": {
84
+ "pageTitle": "Sign Up",
85
+ "title": "Create Account",
86
+ "submit": "Create account",
87
+ "usernamePlaceholder": "e.g: johndoe",
88
+ "displayNamePlaceholder": "e.g: John Doe",
89
+ "created": "Account created successfully",
90
+ "createdDescription": "You still need to verify your email before you can log in.",
91
+ "failed": "Could not create account",
92
+ "almostThere": "Almost there!",
93
+ "verificationSent": "A verification link has been sent to your email. Please check your inbox and follow the instructions to verify your account.",
94
+ "closePage": "You can close this page. You will be logged in automatically once you verify your email.",
95
+ "checkSpam": "If you don't see the email, please check your spam folder."
96
+ }
97
+ },
98
+ "navbar": {
99
+ "defaultUserName": "User",
100
+ "github": "Pluto on GitHub",
101
+ "logout": "Logout",
102
+ "openNavigation": "Open navigation",
103
+ "goHome": "Go to dashboard home",
104
+ "visitSite": "Visit {domain}",
105
+ "viewSite": "View site",
106
+ "openUserMenu": "Open user menu"
107
+ },
108
+ "settings": {
109
+ "general": {
110
+ "title": "General",
111
+ "websiteTitle": "Website Title",
112
+ "websiteTitlePlaceholder": "e.g. My Website",
113
+ "websiteDescription": "Website Description",
114
+ "websiteDescriptionPlaceholder": "e.g. My Website Description",
115
+ "websiteUrl": "Website URL",
116
+ "websiteUrlPlaceholder": "e.g. https://example.com"
117
+ }
118
+ },
119
+ "capabilities": {
120
+ "settingsManage": "Manage site settings",
121
+ "usersRead": "View all user accounts"
122
+ },
123
+ "envWarning": {
124
+ "title": "Connection string not saved automatically",
125
+ "intro": "The database change already succeeded. Pluto could not write {variable} to your project's {file} file, so it will ask again next time unless you add it by hand:",
126
+ "step1": "Open (or create) {file} in your project's root folder.",
127
+ "step2": "Add this line. Replace any existing {variable} line with it.",
128
+ "step3": "Restart the server.",
129
+ "copyLine": "Copy the DATABASE_URL line",
130
+ "copied": "Copied",
131
+ "copiedDescription": "The DATABASE_URL line is on your clipboard.",
132
+ "copyFailed": "Could not copy",
133
+ "copyFailedDescription": "Select and copy the line by hand instead."
134
+ },
135
+ "connection": {
136
+ "connectionString": "Supabase connection string",
137
+ "connectionStringHelp": "You can find your connection string {link}.",
138
+ "connectionStringHelpLink": "clicking here",
139
+ "password": "Database password",
140
+ "passwordInfo": "Your password is created when you create your Supabase project. You can change it in the {link}.",
141
+ "passwordInfoLink": "Supabase Database Settings",
142
+ "whyTitle": "Why do I need to provide a database password?",
143
+ "restartTitle": "This restarts the dev server",
144
+ "restarting": "Dev server restarting",
145
+ "reconnected": "Reconnected",
146
+ "stillReconnecting": "Still reconnecting",
147
+ "stillReconnectingDescription": "The dev server is taking longer than expected. Reload this page in a moment.",
148
+ "checkConsole": "Please check the console for more details."
149
+ },
150
+ "setup": {
151
+ "title": "Setup Wizard",
152
+ "steps": {
153
+ "database": "Database Setup",
154
+ "complete": "Complete Setup"
155
+ },
156
+ "submit": "Set up database",
157
+ "next": "Submit",
158
+ "why": "It will be used to create your database structure. It won't be stored anywhere. That's a {promise}! 😉",
159
+ "promise": "promise",
160
+ "promiseTooltip": "Click on the 'Technical details' button for more info",
161
+ "technicalDetails": "Technical details",
162
+ "technicalDetailsRequest": "When you click \"Next\", a request to {route} will be made with the database password you provided. This API will then use this password to make a direct PostgreSQL connection to your Supabase database and run the SQL queries from this {files}.",
163
+ "technicalDetailsRoute": "a server route",
164
+ "technicalDetailsFiles": "SQL files",
165
+ "technicalDetailsStored": "After the setup is complete, the password will NOT be stored or used for any other purpose.",
166
+ "restartDescription": "Saving your connection string writes it to .env. In development, that restarts the server automatically. The page will briefly show a reconnecting message — this is expected, not an error.",
167
+ "restartingDescription": "Setup is finishing. Reconnecting…",
168
+ "reconnectedDescription": "The server is back. Your database has been set up.",
169
+ "failed": "Database setup failed",
170
+ "failedDescription": "Please check your password and try again.",
171
+ "complete": "Database setup complete",
172
+ "completeDescription": "Your database has been set up successfully.",
173
+ "error": "Error setting up the database",
174
+ "doneTitle": "Setup Complete",
175
+ "doneDescription": "Your database is ready. Create your admin account by signing up.",
176
+ "goToSignup": "Go to sign up"
177
+ },
178
+ "migrations": {
179
+ "title": "Migrations",
180
+ "banner": "{count} layer needs a database migration | {count} layers need a database migration",
181
+ "goTo": "Go to Migrations",
182
+ "loading": "Loading migration status…",
183
+ "upToDate": "Everything is up to date",
184
+ "upToDateDescription": "No layer needs a database migration.",
185
+ "pendingLayers": "Pending layers ({count})",
186
+ "appliedLayers": "Applied layers ({count})",
187
+ "why": "It is used once to open a direct PostgreSQL connection and run the pending layer schemas. It is then written to .env on the server, so future migrations need no prompt. It is never stored anywhere else, and never sent back to your browser.",
188
+ "restartDescription": "Saving a new connection string writes it to .env. In development, that restarts the server automatically. The page will briefly show a reconnecting message — this is expected, not an error.",
189
+ "restartingDescription": "Saving a new connection string restarts the dev server. Reconnecting…",
190
+ "reconnectingBanner": "Saving a new connection string restarts the dev server. Reconnecting — this page will update on its own once it's back.",
191
+ "reconnectedAllApplied": "The server is back. All migrations were applied.",
192
+ "reconnectedPending": "The server is back. {count} layer(s) still pending — check above.",
193
+ "apply": "Apply pending migrations",
194
+ "applied": "Migrations applied",
195
+ "applyFailed": "Could not apply migrations",
196
+ "connectionRequired": "A database connection string is required.",
197
+ "error": "Error applying migrations",
198
+ "lastRun": "Last run",
199
+ "status": {
200
+ "applied": "applied",
201
+ "skipped": "skipped",
202
+ "failed": "failed"
203
+ },
204
+ "loadFailed": "Could not load migration status",
205
+ "loadFailedDescription": "This isn't the dev-server-restart case above — something else went wrong reading migration status. Check the server console, then try again.",
206
+ "retry": "Retry"
207
+ }
208
+ }
209
+ }
@@ -0,0 +1,209 @@
1
+ {
2
+ "supabase": {
3
+ "profile": {
4
+ "migrationPending": "Aplique as migrações pendentes do banco de dados para salvar seu idioma."
5
+ },
6
+ "errors": {
7
+ "notLoggedIn": "Entre na sua conta para continuar.",
8
+ "notAdmin": "Sua conta não é de administrador.",
9
+ "missingPermission": "Sua conta não tem a permissão \"{capability}\".",
10
+ "setupCompleted": "A configuração já foi concluída.",
11
+ "coreSchemaMissing": "Esquema principal não encontrado. Reinstale @plutocms/supabase e reinicie o servidor de desenvolvimento.",
12
+ "fetchUsers": "Erro ao buscar os usuários: {message}",
13
+ "fetchUser": "Erro ao buscar o usuário: {message}",
14
+ "localeUnsupported": "Este idioma não está disponível.",
15
+ "duplicateRecord": "Já existe um registro com este valor.",
16
+ "missingReference": "Isto aponta para um registro que não existe.",
17
+ "migrationStatus": "Erro ao ler o status das migrações: {message}"
18
+ },
19
+ "auth": {
20
+ "backToLogin": "Voltar para o login",
21
+ "passwordMismatch": "As senhas não coincidem.",
22
+ "fields": {
23
+ "email": "E-mail",
24
+ "password": "Senha",
25
+ "username": "Nome de usuário",
26
+ "displayName": "Nome de exibição",
27
+ "confirmPassword": "Confirmar senha"
28
+ },
29
+ "toasts": {
30
+ "loginFailed": "Não foi possível entrar",
31
+ "loggedOut": "Sessão encerrada com sucesso",
32
+ "resetFailed": "Não foi possível enviar o e-mail de redefinição",
33
+ "updateFailed": "Não foi possível atualizar a senha",
34
+ "passwordUpdated": "Senha atualizada com sucesso",
35
+ "notLoggedIn": "Você não está conectado",
36
+ "notLoggedInDescription": "Entre na sua conta para continuar."
37
+ },
38
+ "errors": {
39
+ "unknown": "Ocorreu um erro desconhecido.",
40
+ "invalid_credentials": "E-mail ou senha inválidos.",
41
+ "email_not_confirmed": "Seu e-mail ainda não foi confirmado. Verifique sua caixa de entrada para encontrar o link de verificação.",
42
+ "over_request_rate_limit": "Muitas tentativas. Aguarde um momento e tente novamente.",
43
+ "over_email_send_rate_limit": "Muitos e-mails enviados. Aguarde um momento e tente novamente.",
44
+ "weak_password": "Esta senha é muito fraca. Escolha uma senha mais forte.",
45
+ "same_password": "A nova senha deve ser diferente da senha atual.",
46
+ "user_not_found": "Nenhuma conta foi encontrada para este e-mail.",
47
+ "email_exists": "Já existe uma conta com este e-mail.",
48
+ "user_already_exists": "Já existe uma conta com este e-mail.",
49
+ "signup_disabled": "O cadastro está desativado neste site.",
50
+ "email_address_invalid": "Este endereço de e-mail não é válido."
51
+ },
52
+ "confirm": {
53
+ "pageTitle": "Verificação da conta",
54
+ "title": "Conta verificada!",
55
+ "description": "Sua conta foi verificada com sucesso. Agora você já pode usar o painel.",
56
+ "goToDashboard": "Ir para o painel"
57
+ },
58
+ "forgot": {
59
+ "pageTitle": "Esqueceu a senha?",
60
+ "title": "Esqueceu a senha?",
61
+ "description": "Informe seu e-mail e enviaremos um link de redefinição.",
62
+ "submit": "Enviar link de redefinição",
63
+ "sentTitle": "Verifique sua caixa de entrada",
64
+ "sentDescription": "Se existir uma conta para {email}, enviamos um link de redefinição de senha. Verifique seu e-mail e siga o link."
65
+ },
66
+ "login": {
67
+ "title": "Entrar",
68
+ "submit": "Entrar",
69
+ "createAccount": "Criar conta",
70
+ "backToHome": "Voltar para o início",
71
+ "forgotPassword": "Esqueceu a senha?"
72
+ },
73
+ "update": {
74
+ "title": "Atualizar senha",
75
+ "description": "Informe sua nova senha abaixo.",
76
+ "newPassword": "Nova senha",
77
+ "confirmNewPassword": "Confirmar nova senha",
78
+ "submit": "Atualizar senha",
79
+ "doneTitle": "Senha atualizada!",
80
+ "doneDescription": "Sua senha foi alterada. Agora você já pode entrar com a nova senha.",
81
+ "goToLogin": "Ir para o login"
82
+ },
83
+ "signup": {
84
+ "pageTitle": "Cadastro",
85
+ "title": "Criar conta",
86
+ "submit": "Criar conta",
87
+ "usernamePlaceholder": "ex.: joaosilva",
88
+ "displayNamePlaceholder": "ex.: João Silva",
89
+ "created": "Conta criada com sucesso",
90
+ "createdDescription": "Ainda é preciso verificar seu e-mail para poder entrar.",
91
+ "failed": "Não foi possível criar a conta",
92
+ "almostThere": "Quase lá!",
93
+ "verificationSent": "Enviamos um link de verificação para o seu e-mail. Verifique sua caixa de entrada e siga as instruções para verificar sua conta.",
94
+ "closePage": "Você pode fechar esta página. Você entrará automaticamente assim que verificar seu e-mail.",
95
+ "checkSpam": "Se você não encontrar o e-mail, verifique sua pasta de spam."
96
+ }
97
+ },
98
+ "navbar": {
99
+ "defaultUserName": "Usuário",
100
+ "github": "Pluto no GitHub",
101
+ "logout": "Sair",
102
+ "openNavigation": "Abrir navegação",
103
+ "goHome": "Ir para o início do painel",
104
+ "visitSite": "Visitar {domain}",
105
+ "viewSite": "Ver site",
106
+ "openUserMenu": "Abrir menu do usuário"
107
+ },
108
+ "settings": {
109
+ "general": {
110
+ "title": "Geral",
111
+ "websiteTitle": "Título do site",
112
+ "websiteTitlePlaceholder": "ex.: Meu site",
113
+ "websiteDescription": "Descrição do site",
114
+ "websiteDescriptionPlaceholder": "ex.: Descrição do meu site",
115
+ "websiteUrl": "URL do site",
116
+ "websiteUrlPlaceholder": "ex.: https://exemplo.com"
117
+ }
118
+ },
119
+ "capabilities": {
120
+ "settingsManage": "Gerenciar as configurações do site",
121
+ "usersRead": "Ver todas as contas de usuário"
122
+ },
123
+ "envWarning": {
124
+ "title": "A string de conexão não foi salva automaticamente",
125
+ "intro": "A alteração no banco de dados já foi concluída. O Pluto não conseguiu gravar {variable} no arquivo {file} do seu projeto. Por isso, ele vai pedir de novo na próxima vez, a menos que você adicione a linha manualmente:",
126
+ "step1": "Abra (ou crie) o arquivo {file} na pasta raiz do seu projeto.",
127
+ "step2": "Adicione esta linha. Substitua qualquer linha {variable} existente por ela.",
128
+ "step3": "Reinicie o servidor.",
129
+ "copyLine": "Copiar a linha DATABASE_URL",
130
+ "copied": "Copiado",
131
+ "copiedDescription": "A linha DATABASE_URL está na sua área de transferência.",
132
+ "copyFailed": "Não foi possível copiar",
133
+ "copyFailedDescription": "Selecione e copie a linha manualmente."
134
+ },
135
+ "connection": {
136
+ "connectionString": "String de conexão do Supabase",
137
+ "connectionStringHelp": "Você encontra sua string de conexão {link}.",
138
+ "connectionStringHelpLink": "clicando aqui",
139
+ "password": "Senha do banco de dados",
140
+ "passwordInfo": "Sua senha é criada quando você cria seu projeto no Supabase. Você pode alterá-la em {link}.",
141
+ "passwordInfoLink": "Configurações do banco de dados do Supabase",
142
+ "whyTitle": "Por que preciso informar a senha do banco de dados?",
143
+ "restartTitle": "Isto reinicia o servidor de desenvolvimento",
144
+ "restarting": "Servidor de desenvolvimento reiniciando",
145
+ "reconnected": "Reconectado",
146
+ "stillReconnecting": "Ainda reconectando",
147
+ "stillReconnectingDescription": "O servidor de desenvolvimento está demorando mais que o esperado. Recarregue esta página em instantes.",
148
+ "checkConsole": "Verifique o console para ver mais detalhes."
149
+ },
150
+ "setup": {
151
+ "title": "Assistente de configuração",
152
+ "steps": {
153
+ "database": "Configuração do banco de dados",
154
+ "complete": "Concluir configuração"
155
+ },
156
+ "submit": "Configurar banco de dados",
157
+ "next": "Enviar",
158
+ "why": "Ela será usada para criar a estrutura do seu banco de dados. Ela não será armazenada em nenhum lugar. Isso é uma {promise}! 😉",
159
+ "promise": "promessa",
160
+ "promiseTooltip": "Clique no botão 'Detalhes técnicos' para saber mais",
161
+ "technicalDetails": "Detalhes técnicos",
162
+ "technicalDetailsRequest": "Ao clicar em \"Próximo\", será feita uma requisição para {route} com a senha do banco de dados que você informou. Essa API usará a senha para abrir uma conexão direta do PostgreSQL com seu banco de dados do Supabase e executar as consultas SQL destes {files}.",
163
+ "technicalDetailsRoute": "uma rota do servidor",
164
+ "technicalDetailsFiles": "arquivos SQL",
165
+ "technicalDetailsStored": "Depois que a configuração terminar, a senha NÃO será armazenada nem usada para nenhum outro fim.",
166
+ "restartDescription": "Salvar sua string de conexão a grava no arquivo .env. Em desenvolvimento, isso reinicia o servidor automaticamente. A página vai exibir brevemente uma mensagem de reconexão. Isso é esperado e não é um erro.",
167
+ "restartingDescription": "A configuração está terminando. Reconectando…",
168
+ "reconnectedDescription": "O servidor voltou. Seu banco de dados foi configurado.",
169
+ "failed": "Falha ao configurar o banco de dados",
170
+ "failedDescription": "Verifique sua senha e tente novamente.",
171
+ "complete": "Banco de dados configurado",
172
+ "completeDescription": "Seu banco de dados foi configurado com sucesso.",
173
+ "error": "Erro ao configurar o banco de dados",
174
+ "doneTitle": "Configuração concluída",
175
+ "doneDescription": "Seu banco de dados está pronto. Crie sua conta de administrador fazendo o cadastro.",
176
+ "goToSignup": "Ir para o cadastro"
177
+ },
178
+ "migrations": {
179
+ "title": "Migrações",
180
+ "banner": "{count} camada precisa de uma migração do banco de dados | {count} camadas precisam de uma migração do banco de dados",
181
+ "goTo": "Ir para as migrações",
182
+ "loading": "Carregando o status das migrações…",
183
+ "upToDate": "Tudo está atualizado",
184
+ "upToDateDescription": "Nenhuma camada precisa de migração do banco de dados.",
185
+ "pendingLayers": "Camadas pendentes ({count})",
186
+ "appliedLayers": "Camadas aplicadas ({count})",
187
+ "why": "Ela é usada uma única vez para abrir uma conexão direta do PostgreSQL e executar os esquemas pendentes das camadas. Depois, é gravada no arquivo .env do servidor, e as próximas migrações não precisam pedir a senha. Ela nunca é armazenada em outro lugar e nunca é enviada de volta ao seu navegador.",
188
+ "restartDescription": "Salvar uma nova string de conexão a grava no arquivo .env. Em desenvolvimento, isso reinicia o servidor automaticamente. A página vai exibir brevemente uma mensagem de reconexão. Isso é esperado e não é um erro.",
189
+ "restartingDescription": "Salvar uma nova string de conexão reinicia o servidor de desenvolvimento. Reconectando…",
190
+ "reconnectingBanner": "Salvar uma nova string de conexão reinicia o servidor de desenvolvimento. Reconectando — esta página será atualizada sozinha quando o servidor voltar.",
191
+ "reconnectedAllApplied": "O servidor voltou. Todas as migrações foram aplicadas.",
192
+ "reconnectedPending": "O servidor voltou. {count} camada(s) ainda pendente(s) — verifique acima.",
193
+ "apply": "Aplicar migrações pendentes",
194
+ "applied": "Migrações aplicadas",
195
+ "applyFailed": "Não foi possível aplicar as migrações",
196
+ "connectionRequired": "É necessária uma string de conexão do banco de dados.",
197
+ "error": "Erro ao aplicar as migrações",
198
+ "lastRun": "Última execução",
199
+ "status": {
200
+ "applied": "aplicada",
201
+ "skipped": "ignorada",
202
+ "failed": "falhou"
203
+ },
204
+ "loadFailed": "Não foi possível carregar o status das migrações",
205
+ "loadFailedDescription": "Este não é o caso de reinício do servidor de desenvolvimento descrito acima. Algo diferente deu errado ao ler o status das migrações. Verifique o console do servidor e tente novamente.",
206
+ "retry": "Tentar novamente"
207
+ }
208
+ }
209
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@plutocms/supabase",
3
3
  "type": "module",
4
- "version": "0.9.1",
4
+ "version": "0.10.0",
5
5
  "trustedDependencies": [
6
6
  "@parcel/watcher",
7
7
  "@plutocms/pluto",
@@ -30,7 +30,7 @@
30
30
  "postinstall": "nuxt prepare",
31
31
  "prettier": "prettier --write .",
32
32
  "lint": "eslint .",
33
- "lint:fix": "eslint . --fix && prettier --write .",
33
+ "lint:fix": "eslint . --fix",
34
34
  "typecheck": "nuxi typecheck",
35
35
  "test": "vitest run",
36
36
  "supabase-types": "bun ./scripts/supabase-typegen.ts"
@@ -39,7 +39,7 @@
39
39
  "@nuxt/eslint": "^1.17.0",
40
40
  "@nuxt/ui": "^4.12.0",
41
41
  "@nuxtjs/supabase": "^2.1.0",
42
- "@plutocms/pluto": "^0.13.0",
42
+ "@plutocms/pluto": "^0.14.0",
43
43
  "@plutocms/utils": "^0.2.1",
44
44
  "postgres": "^3.4.9",
45
45
  "supabase": "^2.121.0",
@@ -0,0 +1,39 @@
1
+ import { serverSupabaseClient, serverSupabaseUser } from '#supabase/server'
2
+
3
+ /**
4
+ * The calling user's own admin language. `null` means Automatic: the user
5
+ * has not chosen one. No admin gate: every signed-in user reads their own
6
+ * row. A signed-out caller gets `null`, not a 401, so the client never has
7
+ * to special-case this route's error path.
8
+ *
9
+ * Read `user.sub`, not `user.id`. See server/utils/admin-guard.ts.
10
+ */
11
+ export default defineEventHandler(async (event) => {
12
+ const user = await serverSupabaseUser(event)
13
+
14
+ if (!user) {
15
+ return { locale: null as string | null }
16
+ }
17
+
18
+ const client = await serverSupabaseClient<Database>(event)
19
+ const { data, error } = await client
20
+ .from('profiles')
21
+ .select('locale')
22
+ .eq('id', user.sub)
23
+ .maybeSingle()
24
+
25
+ if (error) {
26
+ // profiles.locale is added by 005_profile_locale.sql. A site that has
27
+ // upgraded this package but not yet applied that migration has no such
28
+ // column. That state is expected and temporary, so degrade to
29
+ // "Automatic" instead of a 500. The same choice is made in
30
+ // server/api/permissions/me.get.ts.
31
+ console.error(
32
+ 'profiles.locale read failed, returning no locale:',
33
+ error.message
34
+ )
35
+ return { locale: null as string | null }
36
+ }
37
+
38
+ return { locale: data?.locale ?? null }
39
+ })
@@ -0,0 +1,71 @@
1
+ import { z } from 'zod'
2
+ import { serverSupabaseClient, serverSupabaseUser } from '#supabase/server'
3
+ import { parseBody } from '../../utils/validate'
4
+
5
+ const localeSchema = z.object({
6
+ locale: z.string().nullable(),
7
+ })
8
+
9
+ /**
10
+ * True when PostgREST says the function does not exist. PGRST202 is
11
+ * PostgREST's own "function not found" code. 42883 is the Postgres code.
12
+ */
13
+ function isMissingFunction(error: {
14
+ code?: string
15
+ message?: string
16
+ }): boolean {
17
+ return (
18
+ error.code === 'PGRST202' ||
19
+ error.code === '42883' ||
20
+ /could not find the function/i.test(error.message ?? '')
21
+ )
22
+ }
23
+
24
+ /**
25
+ * Stores the calling user's admin language. Pass `null` for Automatic.
26
+ *
27
+ * The write goes through `public.set_my_locale()`, a SECURITY DEFINER
28
+ * function from 005_profile_locale.sql. `profiles` has no UPDATE policy on
29
+ * purpose, so a user can never write `is_admin`.
30
+ */
31
+ export default defineEventHandler(async (event) => {
32
+ const user = await serverSupabaseUser(event)
33
+
34
+ if (!user) {
35
+ throw createPlutoError({
36
+ statusCode: 401,
37
+ message: 'You must be logged in.',
38
+ key: 'supabase.errors.notLoggedIn',
39
+ })
40
+ }
41
+
42
+ const { locale } = await parseBody(event, localeSchema)
43
+ const { adminLocales } = useRuntimeConfig().public.plutoI18n
44
+
45
+ if (locale !== null && !adminLocales.includes(locale)) {
46
+ throw createPlutoError({
47
+ statusCode: 400,
48
+ message: 'This language is not supported.',
49
+ key: 'supabase.errors.localeUnsupported',
50
+ })
51
+ }
52
+
53
+ const client = await serverSupabaseClient<Database>(event)
54
+ const { error } = await client.rpc('set_my_locale', {
55
+ new_locale: locale as string,
56
+ })
57
+
58
+ if (error) {
59
+ if (isMissingFunction(error)) {
60
+ throw createPlutoError({
61
+ statusCode: 409,
62
+ message: 'Apply the pending database migrations to save your language.',
63
+ key: 'supabase.profile.migrationPending',
64
+ })
65
+ }
66
+
67
+ throw createError({ statusCode: 500, statusMessage: error.message })
68
+ }
69
+
70
+ return { locale }
71
+ })
@@ -23,9 +23,10 @@ export default defineEventHandler(async (event) => {
23
23
  // server/api/migrations/run.post.ts, which calls it "the whole security
24
24
  // model for this endpoint."
25
25
  if (process.env.DATABASE_URL) {
26
- throw createError({
26
+ throw createPlutoError({
27
27
  statusCode: 403,
28
- statusMessage: 'Setup has already been completed.',
28
+ message: 'Setup has already been completed.',
29
+ key: 'supabase.errors.setupCompleted',
29
30
  })
30
31
  }
31
32
 
@@ -47,10 +48,11 @@ export default defineEventHandler(async (event) => {
47
48
  // it over HTTP means this route needs no `baseUrl` from the caller, and
48
49
  // can no longer be made to fetch or execute SQL from an arbitrary origin.
49
50
  if (!layers.core?.length) {
50
- throw createError({
51
+ throw createPlutoError({
51
52
  statusCode: 500,
52
- statusMessage:
53
+ message:
53
54
  'Core schema not found. Reinstall @plutocms/supabase and restart the dev server.',
55
+ key: 'supabase.errors.coreSchemaMissing',
54
56
  })
55
57
  }
56
58
 
@@ -14,9 +14,11 @@ export default defineEventHandler(async (event) => {
14
14
  .eq('id', id)
15
15
 
16
16
  if (error) {
17
- throw createError({
17
+ throw createPlutoError({
18
18
  statusCode: 500,
19
- statusMessage: `Error fetching user: ${error.message}`,
19
+ message: `Error fetching user: ${error.message}`,
20
+ key: 'supabase.errors.fetchUser',
21
+ params: { message: error.message },
20
22
  })
21
23
  }
22
24
 
@@ -9,9 +9,11 @@ export default defineEventHandler(async (event) => {
9
9
  const { data: users, error } = await client.from('profiles').select('*')
10
10
 
11
11
  if (error) {
12
- throw createError({
12
+ throw createPlutoError({
13
13
  statusCode: 500,
14
- statusMessage: `Error fetching users: ${error.message}`,
14
+ message: `Error fetching users: ${error.message}`,
15
+ key: 'supabase.errors.fetchUsers',
16
+ params: { message: error.message },
15
17
  })
16
18
  }
17
19
 
@@ -45,7 +45,11 @@ export async function requireAdmin(event: H3Event) {
45
45
  const user = await serverSupabaseUser(event)
46
46
 
47
47
  if (!user) {
48
- throw createError({ statusCode: 401, statusMessage: 'You must be logged in.' })
48
+ throw createPlutoError({
49
+ statusCode: 401,
50
+ message: 'You must be logged in.',
51
+ key: 'supabase.errors.notLoggedIn',
52
+ })
49
53
  }
50
54
 
51
55
  const client = await serverSupabaseClient<Database>(event)
@@ -64,7 +68,11 @@ export async function requireAdmin(event: H3Event) {
64
68
  }
65
69
 
66
70
  if (error || !profile?.is_admin) {
67
- throw createError({ statusCode: 403, statusMessage: 'Your account is not an admin.' })
71
+ throw createPlutoError({
72
+ statusCode: 403,
73
+ message: 'Your account is not an admin.',
74
+ key: 'supabase.errors.notAdmin',
75
+ })
68
76
  }
69
77
 
70
78
  return user