Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
13 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 200 additions & 16 deletions README.md

Large diffs are not rendered by default.

50 changes: 44 additions & 6 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,48 @@
include: package:flutter_lints/flutter.yaml

code_style:
line-length: 120

linter:
rules:
prefer_const_constructors: true
prefer_final_locals: true
always_declare_return_types: true
annotate_overrides: true
avoid_print: true # Use logger ou debugPrint

avoid_print: true # Evita o uso de print, que pode ser útil para depuração, mas deve ser removido em produção.
prefer_single_quotes: true # Usa aspas simples em strings para manter a consistência.
prefer_const_constructors: true # Encoraja o uso de construtores constantes quando possível para otimização.
prefer_final_fields: true # Use final para campos que não são reatribuídos, garantindo imutabilidade.
prefer_is_not_empty: true # Incentiva o uso de isNotEmpty em vez de !isEmpty para clareza.
always_use_package_imports: true # Garante que todos os imports sejam feitos com package: para evitar conflitos de nome.
avoid_empty_else: true # Evita blocos else vazios que podem confundir a leitura do código.
avoid_unnecessary_containers: true # Evita o uso desnecessário de Container quando outro widget pode ser mais apropriado.
avoid_redundant_argument_values: false # Evita passar valores padrão redundantes para parâmetros.
prefer_const_literals_to_create_immutables: true # Incentiva o uso de literais const para listas e mapas imutáveis.
prefer_final_locals: true # Use final para variáveis locais que não são reatribuídas, melhorando a legibilidade.
file_names: true # Garante que os nomes de arquivos sigam as convenções de nomenclatura.
unnecessary_new: true # Remove a palavra-chave 'new', que é desnecessária nas versões mais recentes do Dart.
prefer_adjacent_string_concatenation: true # Incentiva a concatenação de strings adjacentes em uma única operação.
prefer_inlined_adds: true # Incentiva o uso de adição em linha em vez de métodos separados, quando possível.
unnecessary_this: true # Remove o uso desnecessário do 'this', que pode ser redundante em alguns contextos.
avoid_return_types_on_setters: true # Evita o uso de tipos de retorno em setters, que devem ser void.
prefer_final_parameters: true # Use final para parâmetros que não são reatribuídos, promovendo a imutabilidade.
omit_local_variable_types: true # Incentiva a omissão de tipos de variáveis locais para permitir a inferência de tipo.
always_declare_return_types: true # Sempre declare tipos de retorno em funções para melhor legibilidade.
prefer_typing_uninitialized_variables: true # Exige que variáveis não inicializadas sejam tipadas.
avoid_dynamic_calls: true # Desencoraja o uso de chamadas dinâmicas, que podem introduzir erros.
use_key_in_widget_constructors: true # Incentiva o uso de chaves nos construtores de widgets para otimização de desempenho.
prefer_const_constructors_in_immutables: true # Incentiva construtores constantes em classes imutáveis.
always_put_required_named_parameters_first: true # Garante que os parâmetros nomeados obrigatórios venham primeiro.
sort_pub_dependencies: true # Ordena as dependências no pubspec.yaml para melhor organização.
unnecessary_const: true # Remove a palavra-chave 'const' onde não é necessária para simplificação do código.
use_setters_to_change_properties: true # Incentiva o uso de setters para modificar propriedades de objetos.
type_annotate_public_apis: true # Exige anotação de tipo para APIs públicas para maior clareza.
prefer_asserts_with_message: true # Incentiva o uso de mensagens em assertivas para facilitar a depuração.
prefer_spread_collections: true # Incentiva o uso de spread em coleções para simplificar a sintaxe.
prefer_mixin: true # Incentiva o uso de mixins em vez de herança para reutilização de código.
avoid_void_async: true # Desencoraja o uso de funções assíncronas que retornam void.
prefer_collection_literals: true # Incentiva o uso de literais de coleção em vez de construtores de coleção.
prefer_is_empty: true # Usa isEmpty em vez de verificar se o comprimento é 0, promovendo a legibilidade.
avoid_escaping_inner_quotes: true # Desencoraja a fuga de aspas internas em strings, que pode tornar o código confuso.
no_leading_underscores_for_local_identifiers: false # Proíbe sublinhados iniciais para identificadores locais, melhorando a clareza.
prefer_null_aware_operators: true # Incentiva o uso de operadores nulos para simplificar verificações nulas.
avoid_positional_boolean_parameters: true # Desencoraja o uso de parâmetros booleanos posicionais, que podem ser confusos.
avoid_implementing_value_types: true # Evita a implementação de tipos de valor, que pode causar problemas de desempenho.
prefer_int_literals: true # Incentiva o uso de literais int para números inteiros para maior clareza.
42 changes: 39 additions & 3 deletions assets/docs/create.sql
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ create table skills (
name text not null,
type text not null, -- Vamos usar: 'mobile', 'web' ou 'tools'
is_highlight boolean default false, -- Se é destaque (true/false)
icon_asset text, -- Caminho do ícone (opcional)
created_at timestamp with time zone default timezone('utc'::text, now()) not null
);

Expand All @@ -43,8 +44,6 @@ create policy "Qualquer um pode ler experiencias" on experiences for select usin
alter table skills enable row level security;
create policy "Qualquer um pode ler skills" on skills for select using (true);

ALTER TABLE skills ADD COLUMN icon_url text;

alter table certificates enable row level security;
create policy "Public Access" on certificates for select using (true);

Expand All @@ -69,4 +68,41 @@ create policy "Admin Delete Skills" on skills for delete to authenticated using
-- Tabela CERTIFICATES
create policy "Admin Insert Certs" on certificates for insert to authenticated with check (true);
create policy "Admin Update Certs" on certificates for update to authenticated using (true);
create policy "Admin Delete Certs" on certificates for delete to authenticated using (true);
create policy "Admin Delete Certs" on certificates for delete to authenticated using (true);


create table app_logs (
id bigserial primary key,
level text, -- info, debug, error, warning
message text,
stack text,
timestamp timestamptz default now(),
user_id uuid -- opcional, se tiver login
);

-- Habilitar RLS
alter table app_logs enable row level security;

-- Permitir inserção de logs (autenticados ou anônimos)
create policy "Qualquer um pode inserir logs"
on app_logs
for insert
with check (true);
Comment on lines +87 to +90
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RLS policy allows anyone (authenticated or anonymous) to insert logs with "with check (true)". While this supports the logging functionality for unauthenticated users, it could be exploited to flood the database with excessive log entries. Consider adding rate limiting at the application level or implementing a more restrictive policy that limits the number of log entries per IP/session.

Suggested change
create policy "Qualquer um pode inserir logs"
on app_logs
for insert
with check (true);
create policy "Limitar inserção de logs por usuário/sessão"
on app_logs
for insert
with check (
(
-- Para usuários autenticados, limite a 10 logs por 10 minutos
user_id is not null AND (
(select count(*) from app_logs
where user_id = app_logs.user_id
and timestamp > (now() - interval '10 minutes')
) < 10
)
)
OR
(
-- Para usuários anônimos, limite a 10 logs por 10 minutos
user_id is null AND (
(select count(*) from app_logs
where user_id is null
and timestamp > (now() - interval '10 minutes')
) < 10
)
)
);

Copilot uses AI. Check for mistakes.

-- Permitir que o usuário selecione apenas seus próprios logs
create policy "Usuário pode ler seus próprios logs"
on app_logs
for select
using (auth.uid() = user_id);

-- Permitir que admins leiam todos os logs
create policy "Admin pode ler todos os logs"
on app_logs
for select
using (auth.uid() IN (
-- Adicione aqui os UUIDs dos usuários admin
-- Exemplo: 'uuid-do-admin-1', 'uuid-do-admin-2'
select id from auth.users where email = 'seu-email-admin@exemplo.com'
));
Comment on lines +102 to +106
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The admin policy relies on a hardcoded email check ('seu-email-admin@exemplo.com') which is clearly a placeholder. This should be updated with actual admin email(s) or better yet, documented with clearer instructions on how to configure admin access. Consider adding a comment explaining that this needs to be updated before deployment, or use a more robust admin role system.

Suggested change
using (auth.uid() IN (
-- Adicione aqui os UUIDs dos usuários admin
-- Exemplo: 'uuid-do-admin-1', 'uuid-do-admin-2'
select id from auth.users where email = 'seu-email-admin@exemplo.com'
));
using (
-- ATENÇÃO: Substitua os valores abaixo pelos UUIDs dos usuários admin antes de implantar em produção.
-- Você pode obter os UUIDs dos admins na tabela auth.users.
auth.uid() IN ('uuid-do-admin-1', 'uuid-do-admin-2')
)

Copilot uses AI. Check for mistakes.


Empty file removed lib/core/constants/.gitkeep
Empty file.
3 changes: 3 additions & 0 deletions lib/core/constants/app_constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class AppStrings {
static const String whatsappUrl =
'https://api.whatsapp.com/send?phone=5582999915558';
static const String emailUrl = 'mailto:franklyn.dev.mobile@gmail.com';

static const String gitHubRepositoriesUrl =
'https://github.com/Franklyn-R-Silva?tab=repositories';
}

class AppColors {
Expand Down
Empty file removed lib/core/theme/.gitkeep
Empty file.
10 changes: 5 additions & 5 deletions lib/core/theme/app_theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';

// Project imports:
import '../constants/app_constants.dart';
import 'package:meu_curriculo_flutter/core/constants/app_constants.dart';

class AppTheme {
// Cores Light
Expand Down Expand Up @@ -33,10 +33,10 @@ class AppTheme {
);

static ThemeData _buildTheme({
required Brightness brightness,
required Color background,
required Color surface,
required Color textColor,
required final Brightness brightness,
required final Color background,
required final Color surface,
required final Color textColor,
}) {
return ThemeData(
useMaterial3: true,
Expand Down
Empty file removed lib/core/utils/.gitkeep
Empty file.
46 changes: 46 additions & 0 deletions lib/core/utils/app_logger.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Package imports:
import 'package:supabase_flutter/supabase_flutter.dart';

final supabase = Supabase.instance.client;

class AppLogger {
static Future<void> log({
required final String level,
required final String message,
final String? stack,
final String? userId,
}) async {
try {
// Se não passar userId, tenta pegar o usuário autenticado
final currentUserId = userId ?? supabase.auth.currentUser?.id;

await supabase.from('app_logs').insert({
'level': level,
'message': message,
'stack': stack,
'user_id': currentUserId,
});
} catch (e) {
// evita que falha no logging quebre o app
// Use apenas print em dev, em produção considere remover ou usar serviço externo
print('⚠️ Erro ao gravar log no Supabase: $e');
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using print() for error logging in production is not recommended. Consider using debugPrint() which is more Flutter-friendly and can be conditionally disabled in release mode, or integrate with a proper logging framework like logger package. The comment mentions considering removal in production, but a clearer strategy would be beneficial.

Copilot uses AI. Check for mistakes.
}
}
Comment on lines +7 to +28
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AppLogger.log method and its helper methods (info, debug, error, warning) lack test coverage. Given that the repository already has comprehensive test coverage for other data layer components (as seen in test/data/repositories/supabase_repository_test.dart), consider adding unit tests to verify logging behavior, error handling in the catch block, and that failed logging doesn't break the application.

Copilot uses AI. Check for mistakes.

// Métodos auxiliares para facilitar o uso
static Future<void> info(final String message, {final String? stack}) async {
await log(level: 'info', message: message, stack: stack);
}

static Future<void> debug(final String message, {final String? stack}) async {
await log(level: 'debug', message: message, stack: stack);
}

static Future<void> error(final String message, {final String? stack}) async {
await log(level: 'error', message: message, stack: stack);
}

static Future<void> warning(final String message, {final String? stack}) async {
await log(level: 'warning', message: message, stack: stack);
}
}
16 changes: 12 additions & 4 deletions lib/core/utils/app_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,30 @@ import 'package:flutter/material.dart';
// Package imports:
import 'package:url_launcher/url_launcher.dart';

// Project imports:
import 'package:meu_curriculo_flutter/core/utils/app_logger.dart';

class AppUtils {
/// Abre uma URL no navegador externo.
/// Retorna false se não conseguir abrir.
static Future<void> launchURL(String url, {BuildContext? context}) async {
final Uri uri = Uri.parse(url);
static Future<void> launchURL(final String url, {final BuildContext? context}) async {
final uri = Uri.parse(url);
try {
if (!await launchUrl(uri, mode: LaunchMode.externalApplication)) {
throw Exception('Could not launch $url');
}
} catch (e) {
} catch (e, stack) {
await AppLogger.log(
level: 'error',
message: e.toString(),
stack: stack.toString(),
);
if (context != null && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Não foi possível abrir o link: $e')),
);
}
debugPrint("Erro ao abrir URL: $e");
debugPrint('Erro ao abrir URL: $e');
}
}
}
Empty file removed lib/data/mocks/.gitkeep
Empty file.
8 changes: 4 additions & 4 deletions lib/data/mocks/mock_data.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Project imports:
import '../models/certificate_model.dart';
import '../models/experience_model.dart';
import '../models/project_model.dart';
import '../models/skill_model.dart';
import 'package:meu_curriculo_flutter/data/models/certificate_model.dart';
import 'package:meu_curriculo_flutter/data/models/experience_model.dart';
import 'package:meu_curriculo_flutter/data/models/project_model.dart';
import 'package:meu_curriculo_flutter/data/models/skill_model.dart';

class MockData {
// --- EXPERIÊNCIAS, PROJETOS E SKILLS ATUALIZADOS ---
Expand Down
Empty file removed lib/data/models/.gitkeep
Empty file.
11 changes: 2 additions & 9 deletions lib/data/models/certificate_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,10 @@ class CertificateModel {
final String date; // Novo

const CertificateModel({
this.id,
required this.title,
required this.description,
required this.credentialUrl,
required this.language,
required this.framework,
required this.issuer,
required this.date,
required this.title, required this.description, required this.credentialUrl, required this.language, required this.framework, required this.issuer, required this.date, this.id,
});

factory CertificateModel.fromMap(Map<String, dynamic> map) {
factory CertificateModel.fromMap(final Map<String, dynamic> map) {
return CertificateModel(
id: map['id'],
title: map['title'] ?? '',
Expand Down
8 changes: 2 additions & 6 deletions lib/data/models/experience_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,11 @@ class ExperienceModel {
final bool isCurrent; // Para destacar se é o emprego atual

const ExperienceModel({
this.id,
required this.role,
required this.company,
required this.period,
required this.description,
required this.role, required this.company, required this.period, required this.description, this.id,
this.isCurrent = false,
});

factory ExperienceModel.fromMap(Map<String, dynamic> map) {
factory ExperienceModel.fromMap(final Map<String, dynamic> map) {
return ExperienceModel(
id: map['id'],
role: map['role'] ?? '',
Expand Down
2 changes: 1 addition & 1 deletion lib/data/models/project_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class ProjectModel {
this.id,
});

factory ProjectModel.fromMap(Map<String, dynamic> map) {
factory ProjectModel.fromMap(final Map<String, dynamic> map) {
return ProjectModel(
id: map['id'],
title: map['title'] ?? '',
Expand Down
8 changes: 4 additions & 4 deletions lib/data/models/skill_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@ class SkillModel {
final SkillType type; // Nova propriedade

const SkillModel({
this.id,
required this.name,
required this.type,
this.id,
this.iconAsset,
this.isHighlight = false,
required this.type, // Obrigatório agora
});

factory SkillModel.fromMap(Map<String, dynamic> map) {
factory SkillModel.fromMap(final Map<String, dynamic> map) {
return SkillModel(
id: map['id'],
name: map['name'] ?? '',
iconAsset: map['icon_asset'],
isHighlight: map['is_highlight'] ?? false,
type: SkillType.values.firstWhere(
(e) => e.name == map['type'],
(final e) => e.name == map['type'],
orElse: () => SkillType.tools,
),
);
Expand Down
Empty file removed lib/data/repositories/.gitkeep
Empty file.
10 changes: 5 additions & 5 deletions lib/data/repositories/portfolio_repository.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Project imports:
import '../mocks/mock_data.dart';
import '../models/certificate_model.dart';
import '../models/experience_model.dart';
import '../models/project_model.dart';
import '../models/skill_model.dart';
import 'package:meu_curriculo_flutter/data/mocks/mock_data.dart';
import 'package:meu_curriculo_flutter/data/models/certificate_model.dart';
import 'package:meu_curriculo_flutter/data/models/experience_model.dart';
import 'package:meu_curriculo_flutter/data/models/project_model.dart';
import 'package:meu_curriculo_flutter/data/models/skill_model.dart';

abstract class IPortfolioRepository {
Future<List<ProjectModel>> getProjects();
Expand Down
Loading
Loading