Commit c0630910 authored by Mahmoud Aglan's avatar Mahmoud Aglan

ashes and blood

parents
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
maxime.gris@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAwNRFitb/O8fS9TaUoCZ/VJZUEehvdyb1EgCjPrQbTeT6TlZh
rvkzHYcGIKsgarI6wuP9aK4+rLW8SL9VP6Ey3G4CgY/Hx9ZxhoSN5N2ffZkJE1Ji
hvgkDXzSN+l4P3e422ICxuVQqozba8/o8pZo46EzgRry760i9RcR8Q8JsXysjeQ1
Q68F8JhUYt1GNQlc5/A1EHEHyv1XIMDkYQ0eart1iUf9uvU/tp6pFTNUq/UtL/BT
RaJdnShbstS7bsfZwkyRtzXUlu15z/xdCsoXbbz+GC4oV7thzZQ+eRS8sZBGTsHF
6AaNqvd3QQnbFEpUSDzK3xupVEvLw3BbwYFvxwIDAQABAoIBAB4Gr9F/yvynD/1p
A1mwxPEJ+4tSU1ENeunTuZfA+eN2PVfHcayKV2BIrzaVDxYuLKI+WC5du5qvLeNy
D7c5xa63XqKIHgbLKKBWsbWqoPQwyU397SOxLgP/pMhaDYRsgxd+Oop4GMiF6IDw
PgjQTQLtDhUTejLCFghuEDgmLE87oi4oV3m8y36Yl1gHSHLzHivk8tiJoFdd9Jw3
FdM9wPS2FcafGaT7CDhbmo8XtHgynxbjCAX6D8tOpsbhVuClseRLXMfhkai0UuO4
JhgJ4tvDoxW3G/3qZkSvL/jUr5gybUCjVAcBfE7HIvfcYKQxU+iX9R8Q7cWzFO/d
RjooSWECgYEA9DDSYxBXuOkVHq9KDWnRBWUslLk2i9nvPEL+JVPqzR6Q+uRnZzl/
j54XBd25lAcCkTDmjZTHKFroX5uvgBzHGfGZFGtoZuObfVkzK9eicupPqTe7rcN6
fTJWeJnNJsYbkGzv0jrqvBJOG+/9zGVsn7UQZvWHiS9lgKYrDz2Vx5cCgYEAyieS
3xFK+lytLgJ6pqNx6RuvEAKgouZi3sgYIxwyMSA9Yap/5po6Osj8w9X18Bvm6YYF
gok+Zx63pEB7296RrmGDxkOw5Hl/gH07Yx2hvM4et3RyvK3udOXCdcXgWN0ue/Uf
H75UZ4CLAmNALEUa9lOcB2uydVHOhXCmgPveH1ECgYAtShzLKM3MStaS8VnfsP+G
a6RgFRXrzEjVuWsfizfiQUgMcG5JM93Xyi9k9CGmNcKhIRuxqKVjc7DjgqGDNlMr
GacVpXIgmxhMoE2gVQcZHyIVNXQGn1nJfJuTFJt7FIUqPTohmLHOneqEvfcpgKor
2M4o+mLf6718pdUYp4hvEwKBgBSJhLBIz3cz5xwfgFphjHcEKvrTaYJjKXQ8m8cl
XCwFfHbpnWjODlBejt9OY1frXcAnr3Odgct0IW/8ZRjnOaGfooWH5vavKTbigiAF
qKLHxfMZT3a/rNQPa3wPiEU+4zQQqQLOkUCanIS3lJNqydxwjg9q74xfrT19Pk0o
SV6hAoGBAKfidUGqWGH3FugbgG2cm7rK54nh978brZLKglekR1RlRWKG7QpRP33v
D13y3BD1rRM3vguD2aABhwqbYVt1hjHA+mv+yDzJps08FtZIasiTRpm2mFanOD84
yKf+0/HMD2G45HzoMYdG6BdZ5HP1y4WFNfRoxjwCTnwyrDMNJhOl
-----END RSA PRIVATE KEY-----
# XadrezSuíço Emparceirador
Seja bem-vindo ao XadrezSuíço Emparceirador - o primeiro software open-source e gratuito emparceirador de Eventos/Torneios de Xadrez.
### O que é o XadrezSuíço
O projeto XadrezSuíço é um conjunto de soluções para facilitar o gerenciamento de eventos de Xadrez. Com o que é fornecido no projeto é possível gerenciar circuitos e eventos de Xadrez. Para saber melhor sobre o projeto, [acesse o site do projeto](http://xadrezsuico.github.io).
## O Emparceirador
O XadrezSuíço Emparceirador é um software de emparceiramento para uso em eventos de Xadrez, onde auxilia os árbitros ou professores a gerenciar o emparceiramento e classificação de eventos da modalidade.
O objetivo do projeto era permitir aos árbitros e professores que não possuem condição para adquirir o Swiss-Manager (software de gerenciamento homologado pela CBX (Confederação Brasileira de Xadrez) e FIDE (Federação Internacional de Xadrez)) possuam um software atual e legal (visto que muitos usam de forma ilegal o software Swiss Perfect 98, ou seja, sem licença adquirida) para gerenciamento de torneios de Xadrez.
Para conhecer um pouco melhor o software, você pode assistir os seguintes vídeos:
- Apresentação e Tutorial do Software: [https://www.youtube.com/watch?v=0kKUva1GTaA](https://www.youtube.com/watch?v=0kKUva1GTaA).
- Palestra de Lançamento durante o 19º Congresso Latino-americano de Software Livre e Tecnologias Abertas - A palestra começa no tempo de 5h17m56s, o link leva até este tempo: [https://youtu.be/IZBkEWjwDQE](https://youtu.be/IZBkEWjwDQE?t=19075).
### Desenvolvimento
O projeto foi desenvolvido em uma mescla de Web com Desktop, utilizando-se de Angular com Bootstrap para desenvolvimento da Interface Gráfica e da biblioteca Electron.js para empacotamento e também para funcionar como um "backend".
Para facilitar o início do projeto, foi utilizado um _boilerplate_ para início do desenvolvimento, o [maximegris/angular-electron](https://github.com/maximegris/angular-electron), onde já forneceu o angular e o electron já devidamente configurados para início do projeto.
Então, caso deseja baixar o código e executar em sua máquina, é possível seguir o tutorial de início do _angular-electron_ ou então seguir os seguintes comandos abaixo:
git clone git@github.com:XadrezSuico/Emparceirador.git
cd Emparceirador
npm install
cd app
npm install
cd ..
Estando na pasta raiz do projeto, é possível então rodar o software localmente através do código. Vale constar que, há uma integração do electron através do _ipcMain_ e do _ipcRenderer_ com a emissão de eventos onde ocorre a operação do sistema. É possível ter mais informações sobre essa intercomunicação [através deste link](https://www.electronjs.org/docs/latest/tutorial/ipc).
Então, para que o software rode corretamente é necessário executar tanto o angular quanto o electron, e o seguinte comando faz isto:
npm run electron:local
Este comando efetua o _build_ do código-fonte do Angular e gera um empacotamento para rodar em teste do Electron junto com o código do _front-end_ compilado, digamos assim.
Há outros comandos existentes do projeto do _angular-electron_, porém, não recomendo muito a utilização. Segue a lista:
| Comando | Descrição |
|--------------------------|-------------------------------------------------------------------------------------------------------|
| `npm run ng:serve` | Executa o front-end no navegador (Modo desenvolvedor) |
| `npm run web:build` | Efetua o _build_ do código-fonte do Front-end Angular para ser possível o uso diretamente no navegador. Os arquivos compilados estarão disponíveis na pasta /dist. |
| `npm run electron:local` | Efetua o _build_ do código-fonte do Front-end Angular e inicia o Electron localmente. (RECOMANDADO) |
| `npm run electron:build` | fetua o _build_ do código-fonte do Front-end Angular e compila um aplicativo de acordo com o seu sistema operacional. |
Para mais informações sobre o "core" do _angular-electron_ é possível obter no repositório do mesmo, acessível através [deste link](https://github.com/maximegris/angular-electron).
#### Ideia básica de funcionamento do software
O XadrezSuíço Emparceirador quando executado possui um banco de dados `sqlite` que é salvo dentro da pasta de dados do usuário do software. Neste banco de dados é onde fica armazenado os dados do software.
O sistema possui um arquivo que é identificado com a extensão `.xadrezsuico-json`, porém, ele serve apenas para ter uma cópia do torneio salva. É como se fosse um arquivo de exportação que toda vez que ocorre uma Transação no Banco de Dados (INSERT, UPDATE ou DELETE) ele salva a estrutura do evento com todos os seus dados dentro deste arquivo. Os momentos que é executado essa exportação são gerenciados pelo próprio código através de eventos, onde sempre chega ao evento dentro do Controller do Evento onde efetua de fato a chamada da função que executa a exportação.
Com este mesmo arquivo é possível importar o evento para outra instância do XadrezSuíço Emparceirador e quando importado, o software registra que o arquivo de exportação é exatamente este para armazenar as alterações quando necessário.
O arquivo de exportação, além das infomações do evento ele possui um hash de conferência dos dados, onde ele consegue identificar se o arquivo teve algum tipo de alteração. O Hash é gerado em SHA1.
O sistema se baseia na ideia de eventos de xadrez, e não torneios como os outros softwares trabalham. Geralmente é necessário possuir um arquivo criado para cada torneio que é operado e quando necessita-se trabalhar com um evento com mais do que um torneio, é necessário possuir mais do que um torneio aberto para operação. Com isso a ideia é que em um evento possua de 1 a mais torneios, onde não necessite a abertura de mais do que um "arquivo" por vez.
Para o emparceiramento Suíço, atualmente é utilizado um pequeno algoritmo criado por mim, porém, que estarei alterando em breve, para uma biblioteca já existente ou então para algo mais robusto, visto que este ainda não atende os requisitos de evitar que um enxadrista jogue duas vezes com a mesma pessoa e também sobre evitar que jogue mais de duas vezes seguidas com a mesma cor.
Quanto ao emparceiramento Todos-contra-todos, também conhecido como Round-robin ou Schüring, é utilizada a biblioteca [`robin.js`](https://github.com/pensierinmusica/robin-js), onde gera o emparceiramento do torneio como um todo para o sistema.
Há a ideia de implementar mais funcionalidades, dentre elas:
- Mais critérios de desempate;
- Possibilidade de permitir um resultado maior que um (para turno e returno, por exemplo);
- Possibilidade de emparceiramento de torneios por equipes;
- Dentre outras que irei atualizar por aqui de acordo com o que visualizar que necessitar ou das _issues_ criadas.
Se quiser trabalhar no desenvolvimento, fique a vontade, efetue um _fork_ e pode colocar a mão na massa. :)
Qualquer dúvida, pode contactar-me no telegram como [arroba]jppcel.
Para saber das novidades do XadrezSuíço Emparceirador, você pode entrar nos grupos:
- Telegram: [@XadrezSuicoEmparceirador](https://t.me/+z4yuiwIuSt9hNjlh)
- Whatsapp [https://chat.whatsapp.com/KJAKo2Bp09CGQC7xMq4RBN](https://chat.whatsapp.com/KJAKo2Bp09CGQC7xMq4RBN)
@@ -0,0 +1,215 @@
================================================================================
AL-ARCADE SELF-HOSTED SUPABASE — CONNECTION & REFERENCE
================================================================================
SERVER ACCESS
=============
IP: 3.68.63.185
User: ubuntu
SSH Key: NewServer.pem
SSH Command: ssh -i NewServer.pem ubuntu@3.68.63.185
All docker commands require sudo.
SUPABASE API URL
================
https://safe-supabase-kong.caprover.al-arcade.com
SUPABASE STUDIO (Dashboard)
============================
URL: https://safe-supabase-studio.caprover.al-arcade.com
Auth: HTTP Basic Auth
Username: admin
Password: Alarcade123#
API KEYS
========
Anon Key (public, client-side safe):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzM1Njg5NjAwLCJleHAiOjE4OTM0NTYwMDB9.31PF6PvP-pSrvRuQwLFptQoejR0W1A7o53lZhEbnz84
Service Role Key (secret, server-side only, bypasses RLS):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaXNzIjoic3VwYWJhc2UiLCJpYXQiOjE3MzU2ODk2MDAsImV4cCI6MTg5MzQ1NjAwMH0.wNfmuJNkX-bZwD7RbjxOChlRf_3Xm4I7bswEYTcDCg4
JWT Secret:
902343981eb82f43ff7a3757f3fcf25f14a2b9c729454eae5029ee3d1f189eb7
DATABASE DIRECT CONNECTION
==========================
Host: safe-supabase-db (internal) or localhost from server
Port: 5432
Database: postgres
Admin User: supabase_admin
Password: 28ac17bf9d4f7a3d1bad045408102cf5
Connection String (from server):
postgresql://supabase_admin:28ac17bf9d4f7a3d1bad045408102cf5@localhost:5432/postgres
Connection Pooler (Supavisor):
Port 6543 (transaction mode)
API ENDPOINTS
=============
All endpoints are relative to the API URL above.
All require header: apikey: <anon_key or service_role_key>
REST API: /rest/v1/
Auth: /auth/v1/
Storage: /storage/v1/
Realtime: /realtime/v1/
Edge Functions: /functions/v1/<function_name>
GraphQL: /graphql/v1
Postgres Meta: /pg/
CLIENT SDK SETUP
================
JavaScript/TypeScript:
----------------------
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'https://safe-supabase-kong.caprover.al-arcade.com',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzM1Njg5NjAwLCJleHAiOjE4OTM0NTYwMDB9.31PF6PvP-pSrvRuQwLFptQoejR0W1A7o53lZhEbnz84'
)
Unity C#:
---------
var url = "https://safe-supabase-kong.caprover.al-arcade.com";
var key = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzM1Njg5NjAwLCJleHAiOjE4OTM0NTYwMDB9.31PF6PvP-pSrvRuQwLFptQoejR0W1A7o53lZhEbnz84";
var client = new Supabase.Client(url, key);
Flutter/Dart:
-------------
final supabase = Supabase.initialize(
url: 'https://safe-supabase-kong.caprover.al-arcade.com',
anonKey: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzM1Njg5NjAwLCJleHAiOjE4OTM0NTYwMDB9.31PF6PvP-pSrvRuQwLFptQoejR0W1A7o53lZhEbnz84',
);
Python:
-------
from supabase import create_client
supabase = create_client(
"https://safe-supabase-kong.caprover.al-arcade.com",
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzM1Njg5NjAwLCJleHAiOjE4OTM0NTYwMDB9.31PF6PvP-pSrvRuQwLFptQoejR0W1A7o53lZhEbnz84"
)
AVAILABLE FEATURES
==================
1. DATABASE (PostgreSQL 15)
- Create tables, schemas, views, functions, triggers
- Row Level Security (RLS) policies
- Extensions: pgvector, pg_graphql, pgjwt, uuid-ossp, pgcrypto
- Full SQL access via psql or REST API
2. AUTHENTICATION (GoTrue v2.186.0)
- Email/password sign-up and login
- Anonymous users
- JWT-based sessions
- Admin user management API
- Auto-confirm enabled (no SMTP configured yet)
3. STORAGE (v1.22.12)
- Create buckets (public or private)
- Upload/download files up to 50MB
- Image transformations via ImgProxy
- RLS policies on buckets/objects
4. REALTIME (v2.34.47)
- Postgres Changes (subscribe to INSERT/UPDATE/DELETE)
- Broadcast (send messages between clients)
- Presence (track online users)
- Enable per table: ALTER PUBLICATION supabase_realtime ADD TABLE <table_name>;
5. EDGE FUNCTIONS (Deno runtime v1.71.2)
- Deploy at /captain/data/safe-supabase/functions/
- Each function is a folder with index.ts
- Accessible at /functions/v1/<function_name>
6. REST API (PostgREST v12.2.8)
- Auto-generated REST endpoints for all tables
- Filtering, pagination, ordering, embedding (joins)
- Respects RLS policies based on JWT role
7. GRAPHQL (pg_graphql)
- Auto-generated GraphQL schema from tables
- Endpoint: /graphql/v1
8. CONNECTION POOLING (Supavisor 2.7.4)
- Transaction mode on port 6543
- Max 100 client connections, pool size 20
9. IMAGE TRANSFORMATION (ImgProxy v3.30.1)
- Resize, crop, format conversion
- WebP auto-detection
10. ANALYTICS (Logflare 1.36.1)
- PostgreSQL backend (not BigQuery)
- Log collection via Vector
MANAGING VIA SSH (for AI agents)
================================
Run SQL:
sudo docker exec safe-supabase-db psql -U supabase_admin -d postgres -c "YOUR SQL HERE"
Create a table:
sudo docker exec safe-supabase-db psql -U supabase_admin -d postgres -c "
CREATE TABLE public.my_table (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
created_at timestamptz DEFAULT now(),
name text NOT NULL
);
ALTER TABLE public.my_table ENABLE ROW LEVEL SECURITY;
"
Enable Realtime on a table:
sudo docker exec safe-supabase-db psql -U supabase_admin -d postgres -c "
ALTER PUBLICATION supabase_realtime ADD TABLE public.my_table;
"
Create a storage bucket:
curl -X POST http://localhost:8787/storage/v1/bucket \
-H 'apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaXNzIjoic3VwYWJhc2UiLCJpYXQiOjE3MzU2ODk2MDAsImV4cCI6MTg5MzQ1NjAwMH0.wNfmuJNkX-bZwD7RbjxOChlRf_3Xm4I7bswEYTcDCg4' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaXNzIjoic3VwYWJhc2UiLCJpYXQiOjE3MzU2ODk2MDAsImV4cCI6MTg5MzQ1NjAwMH0.wNfmuJNkX-bZwD7RbjxOChlRf_3Xm4I7bswEYTcDCg4' \
-H 'Content-Type: application/json' \
-d '{"id":"my-bucket","name":"my-bucket","public":true}'
List auth users:
curl http://localhost:8787/auth/v1/admin/users \
-H 'apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaXNzIjoic3VwYWJhc2UiLCJpYXQiOjE3MzU2ODk2MDAsImV4cCI6MTg5MzQ1NjAwMH0.wNfmuJNkX-bZwD7RbjxOChlRf_3Xm4I7bswEYTcDCg4' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaXNzIjoic3VwYWJhc2UiLCJpYXQiOjE3MzU2ODk2MDAsImV4cCI6MTg5MzQ1NjAwMH0.wNfmuJNkX-bZwD7RbjxOChlRf_3Xm4I7bswEYTcDCg4'
Deploy an edge function:
# Create function directory on server
sudo mkdir -p /captain/data/safe-supabase/functions/my-function
# Write index.ts to the function directory
# Function becomes available at /functions/v1/my-function
Restart a service:
sudo docker restart safe-supabase-<service>
# Services: db, kong, auth, rest, realtime, storage, functions, meta, analytics, supavisor, imgproxy, vector, studio
View service logs:
sudo docker logs safe-supabase-<service> --tail 50
DOCKER COMPOSE LOCATION
========================
/captain/data/safe-supabase/compose/docker-compose.yml
PERSISTENT DATA
===============
/captain/data/safe-supabase/db/data — PostgreSQL data
/captain/data/safe-supabase/storage — File uploads
/captain/data/safe-supabase/functions — Edge functions code
/captain/data/safe-supabase/kong — Kong config
IMPORTANT NOTES
===============
- This is a SINGLE PROJECT deployment (not multi-tenant like supabase.com)
- All apps/games share the same database — use schemas or table prefixes to organize
- The anon key is safe to embed in client apps (RLS protects data)
- The service role key must NEVER be in client code (it bypasses all security)
- Always enable RLS on tables and write policies before exposing to clients
- Database is NOT exposed to the internet — only accessible via Kong API or SSH
- Expires: JWT keys expire in ~5 years (2030-01-01)
================================================================================
theme: jekyll-theme-architect
\ No newline at end of file
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"cli": {
"defaultCollection": "@angular-eslint/schematics",
"analytics": false
},
"version": 1,
"newProjectRoot": "projects",
"projects": {
"angular-electron": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"schematics": {
"@schematics/angular:application": {
"strict": true
},
"@schematics/angular:component": {
"style": "scss"
}
},
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "src/tsconfig.app.json",
"polyfills": "src/polyfills.ts",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": [],
"customWebpackConfig": {
"path": "./angular.webpack.js",
"replaceDuplicatePlugins": true
}
},
"configurations": {
"dev": {
"optimization": false,
"outputHashing": "none",
"sourceMap": true,
"namedChunks": false,
"aot": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": false,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.dev.ts"
}
]
},
"production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
]
},
"web": {
"optimization": false,
"outputHashing": "none",
"sourceMap": true,
"namedChunks": false,
"aot": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": false,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.web.ts"
}
]
},
"web-production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.web.prod.ts"
}
]
}
}
},
"serve": {
"builder": "@angular-builders/custom-webpack:dev-server",
"options": {
"browserTarget": "angular-electron:build"
},
"configurations": {
"dev": {
"browserTarget": "angular-electron:build:dev"
},
"production": {
"browserTarget": "angular-electron:build:production"
},
"web": {
"browserTarget": "angular-electron:build:web"
},
"web-production": {
"browserTarget": "angular-electron:build:web-production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "angular-electron:build"
}
},
"test": {
"builder": "@angular-builders/custom-webpack:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills-test.ts",
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"inlineStyleLanguage": "scss",
"scripts": [],
"styles": [
"src/styles.scss"
],
"assets": [
"src/favicon.ico",
"src/assets"
],
"customWebpackConfig": {
"path": "./angular.webpack.js",
"replaceDuplicatePlugins": true
}
}
},
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": [
"src/**/*.ts",
"src/**/*.html"
]
}
}
}
},
"angular-electron-e2e": {
"root": "e2e",
"projectType": "application",
"architect": {
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": [
"e2e/**/*.ts"
]
}
}
}
}
},
"defaultProject": "angular-electron"
}
//Polyfill Node.js core modules in Webpack. This module is only needed for webpack 5+.
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
/**
* Custom angular webpack configuration
*/
module.exports = (config, options) => {
config.target = 'electron-renderer';
if (options.fileReplacements) {
for(let fileReplacement of options.fileReplacements) {
if (fileReplacement.replace !== 'src/environments/environment.ts') {
continue;
}
let fileReplacementParts = fileReplacement['with'].split('.');
if (fileReplacementParts.length > 1 && ['web'].indexOf(fileReplacementParts[1]) >= 0) {
config.target = 'web';
}
break;
}
}
config.plugins = [
...config.plugins,
new NodePolyfillPlugin({
excludeAliases: ["console"]
})
];
return config;
}
const database = require('../db/db');
const Categories = require('../models/category.model');
const { ipcMain } = require('electron');
const { uuid } = require('uuidv4');
const dateHelper = require("../helpers/date.helper");
module.exports.setEvents = (ipcMain) => {
database.sync();
ipcMain.handle('controller.categories.listAll', listAll)
ipcMain.handle('controller.categories.listFromTournament', listFromTournament)
ipcMain.handle('controller.categories.create', create)
ipcMain.handle('controller.categories.get', get)
ipcMain.handle('controller.categories.update', update)
ipcMain.handle('controller.categories.remove', remove)
ipcMain.addListener("controller.categories.need_export", need_export);
}
module.exports.create = create;
module.exports.import = Import;
module.exports.listAll = listAll;
module.exports.listFromTournament = listFromTournament;
module.exports.get = get;
module.exports.update = update;
module.exports.remove = remove;
async function need_export(category_uuid){
let category_request = await get(null,category_uuid);
if (category_request.ok === 1) {
ipcMain.emit("controller.tournaments.need_export", category_request.category.tournament_uuid);
}
}
async function create(event, tournament_uuid, category, is_import = false){
try {
let resultadoCreate = await Categories.create({
name: category.name,
abbr: category.abbr,
tournamentUuid: tournament_uuid,
})
// console.log(resultadoCreate);
need_export(resultadoCreate.uuid);
return {ok:1,error:0,data:{uuid:resultadoCreate.uuid}};
} catch (error) {
console.log(error);
}
}
async function Import(event, tournament_uuid, category) {
try {
let resultadoCreate = await Categories.create({
uuid: (category.uuid) ? category.uuid : uuid(),
name: category.name,
abbr: category.abbr,
tournamentUuid: tournament_uuid,
})
// console.log(resultadoCreate);
return { ok: 1, error: 0, data: { uuid: resultadoCreate.uuid } };
} catch (error) {
console.log(error);
}
}
async function listAll() {
try {
let categories = await Categories.findAll();
let categories_return = [];
let i = 0;
for(let category of categories){
let category_return = {
uuid: category.uuid,
name: category.name,
abbr: category.abbr,
tournament_uuid: category.tournamentUuid,
};
categories_return[i++] = category_return;
}
return {ok:1,error:0,categories:categories_return};
} catch (error) {
console.log(error);
}
}
async function listFromTournament(event,tournament_uuid) {
try {
let categories = await Categories.findAll({
where: {
tournamentUuid: tournament_uuid
}
});
let categories_return = [];
let i = 0;
for(let category of categories){
let category_return = {
uuid: category.uuid,
name: category.name,
abbr: category.abbr,
tournament_uuid: category.tournamentUuid,
};
categories_return[i++] = category_return;
}
return {ok:1,error:0,categories:categories_return};
} catch (error) {
console.log(error);
}
}
async function get(e,uuid) {
try {
let category = await Categories.findByPk(uuid);
if (category) {
let category_return = {
uuid: category.uuid,
name: category.name,
abbr: category.abbr,
tournament_uuid: category.tournamentUuid,
};
return { ok: 1, error: 0, category: category_return };
}
return { ok: 0, error: 1, message: "Categoria não encontrada" }
} catch (error) {
console.log(error);
}
}
async function update(e,category){
try {
let resultado = await Categories.update({
name: category.name,
abbr: category.abbr,
},{
where:{
uuid:category.uuid
}
})
// console.log(resultado);
need_export(category.uuid);
return {ok:1,error:0};
} catch (error) {
console.log(error);
}
}
async function remove(e, uuid, is_delete_event = false) {
try {
let category = await Categories.findByPk(uuid);
if(category){
Categories.destroy({
where: {
uuid: uuid
}
});
if (!is_delete_event) ipcMain.emit("controller.tournaments.need_export", category.tournamentUuid);
return {ok:1,error:0};
}else{
return {ok:0,error:1,message:"Categoria não encontrada"};
}
} catch (error) {
console.log(error);
}
}
const TournamentsController = require("./tournament.controller")
const CategoriesController = require("./category.controller")
const PlayersController = require("./player.controller")
const RoundsController = require("./round.controller")
const StandingsController = require("./standing.controller")
const PairingsController = require("./pairing.controller")
const Events = require('../models/event.model');
module.exports.remove = remove;
async function remove(uuid) {
try {
let event = await Events.findByPk(uuid);
let tournaments_request = await TournamentsController.listFromEvent(null,uuid);
if(tournaments_request.ok === 1){
for (let tournament of tournaments_request.tournaments){
let rounds_request = await RoundsController.listByTournament(null,tournament.uuid);
if(rounds_request.ok === 1){
for (let round of rounds_request.rounds) {
let standings_request = await StandingsController.listFromRound(null, tournament.uuid, round.uuid);
if (standings_request.ok === 1) {
for (let standing of standings_request.standings) {
console.log("standing-d-".concat(standing.uuid));
await StandingsController.remove(standing.uuid);
}
}
let pairings_request = await PairingsController.listByRound(null, round.uuid);
if(pairings_request.ok === 1){
for (let pairing of pairings_request.pairings) {
console.log("pairing-d-".concat(pairing.uuid));
await PairingsController.remove(pairing.uuid);
}
}
console.log("round-d-".concat(round.uuid));
await RoundsController.remove(round.uuid)
}
}
let players_request = await PlayersController.listByTournament(null,tournament.uuid);
if(players_request.ok === 1){
for (let player of players_request.players) {
console.log("player-d-".concat(player.uuid));
await PlayersController.remove(null,player.uuid,true);
}
}
let categories_request = await CategoriesController.listFromTournament(null,tournament.uuid);
if(categories_request.ok === 1){
for (let category of categories_request.categories) {
console.log("category-d-".concat(category.uuid));
await CategoriesController.remove(null,category.uuid,true);
}
}
console.log("tournament-d-".concat(tournament.uuid));
await TournamentsController.remove(null, tournament.uuid);
}
}
await Events.destroy({
where:{
uuid: uuid
}
});
return {ok:1,error:0};
}catch(error){
console.log(error);
}
return { ok: 0, error: 1, message: "Erro ainda desconhecido" };
}
const { BrowserWindow } = require('electron');
const { uuid } = require('uuidv4');
const database = require('../db/db');
const Events = require('../models/event.model');
const ImportExportController = require("./import-export.controller");
const EventDeleteController = require("./event-delete.controller");
const dateHelper = require("../helpers/date.helper");
const { ipcMain } = require('electron');
module.exports.setEvents = (ipcMain) => {
ipcMain.handle('controller.events.listAll', listAll)
ipcMain.handle('controller.events.create.example', createExample)
ipcMain.handle('controller.events.create', create)
ipcMain.handle('controller.events.get', get)
ipcMain.handle('controller.events.update', update)
ipcMain.handle('controller.events.remove', remove)
ipcMain.handle('controller.events.delete', remove)
ipcMain.addListener("controller.events.need_export", need_export);
}
module.exports.get = get
module.exports.create = create
module.exports.import = Import
module.exports.update = update
module.exports.remove = remove
module.exports.listAll = listAll
async function need_export(event_uuid) {
let event_request = await get(null, event_uuid);
if (event_request.ok === 1) {
ipcMain.emit("controller.import-export.export_event", event_uuid);
}
}
async function create(e, event, is_import = false){
try {
await database.sync();
console.log("create event");
console.log(event);
const resultadoCreate = await Events.create({
name: event.name,
date_start: dateHelper.convertToSql(event.date_start),
date_finish: dateHelper.convertToSql(event.date_finish),
time_control: event.time_control,
place: event.place,
file_path: event.file_path,
})
ipcMain.emit("controller.import-export.export_event",resultadoCreate.uuid);
return {ok:1,error:0,data:{uuid:resultadoCreate.uuid}};
} catch (error) {
console.log(error);
}
}
async function Import(e, event) {
try {
await database.sync();
console.log(event);
const resultadoCreate = await Events.create({
uuid: (event.uuid) ? event.uuid : uuid(),
name: event.name,
date_start: dateHelper.convertToSql(event.date_start),
date_finish: dateHelper.convertToSql(event.date_finish),
time_control: event.time_control,
place: event.place,
file_path: event.file_path,
})
return { ok: 1, error: 0, data: { uuid: resultadoCreate.uuid } };
} catch (error) {
console.log(error);
}
}
async function createExample(){
try {
const resultado = await database.sync();
// console.log(resultado);
const resultadoCreate = await Events.create({
uuid: '6bcf27ea-7cf9-493b-b71a-f544d98607d0',
name: 'Evento X',
date_start: '2022-11-01',
date_finish: '2022-11-01',
time_control: 'RPD',
place: 'Local',
})
// console.log(resultadoCreate);
} catch (error) {
console.log(error);
}
}
async function listAll() {
try {
const events = await Events.findAll();
let events_return = [];
let i = 0;
for(let event of events){
let event_return = {
uuid: event.uuid,
name: event.name,
date_start: dateHelper.convertToBr(event.date_start),
date_finish: dateHelper.convertToBr(event.date_finish),
place: event.place,
time_control: event.time_control,
file_path: event.file_path,
};
events_return[i++] = event_return;
}
return {ok:1,error:0,events:events_return};
} catch (error) {
console.log(error);
}
}
async function get(e,uuid) {
try {
const event = await Events.findByPk(uuid);
if(event){
let event_return = {
uuid: event.uuid,
name: event.name,
date_start: dateHelper.convertToBr(event.date_start),
date_finish: dateHelper.convertToBr(event.date_finish),
place: event.place,
time_control: event.time_control,
file_path: event.file_path,
};
return { ok: 1, error: 0, event: event_return };
}else{
return { ok: 0, error: 1, message: "Evento não encontrado", not_found: true };
}
} catch (error) {
console.log(error);
}
return { ok: 0, error: 1, message: "Erro ainda desconhecido", not_found: false };
}
async function update(e,event){
try {
let resultado = await Events.update({
name: event.name,
date_start: dateHelper.convertToSql(event.date_start),
date_finish: dateHelper.convertToSql(event.date_finish),
place: event.place,
time_control: event.time_control,
file_path: event.file_path,
},{
where:{
uuid:event.uuid
}
})
// console.log(resultado);
ipcMain.emit("controller.import-export.export_event", event.uuid);
return {ok:1,error:0};
} catch (error) {
console.log(error);
}
}
async function remove(e,uuid) {
try {
let event = await Events.findByPk(uuid);
if(event){
await EventDeleteController.remove(uuid);
return {ok:1,error:0};
}else{
return {ok:0,error:1,message:"Evento não encontrado"};
}
} catch (error) {
console.log(error);
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
const database = require('../db/db');
const Tournaments = require('../models/tournament.model');
const Categories = require('../models/category.model');
const Events = require('../models/event.model');
const { ipcMain } = require('electron');
const { uuid } = require('uuidv4');
const PairingsController = require('./pairing.controller');
const dateHelper = require("../helpers/date.helper");
const TournamentDTO = require("../dto/tournament.dto");
module.exports.setEvents = (ipcMain) => {
database.sync();
ipcMain.handle('controller.tournaments.listAll', listAll)
ipcMain.handle('controller.tournaments.listFromEvent', listFromEvent)
ipcMain.handle('controller.tournaments.create', create)
ipcMain.handle('controller.tournaments.get', get)
ipcMain.handle('controller.tournaments.update', update)
ipcMain.addListener("controller.tournaments.need_export", need_export);
}
module.exports.listAll = listAll;
module.exports.listFromEvent = listFromEvent;
module.exports.create = create;
module.exports.import = Import;
module.exports.get = get;
module.exports.update = update;
module.exports.getPlayerPoints = getPlayerPoints;
module.exports.remove = remove;
async function need_export(tournament_uuid) {
let tournament_request = await get(null, tournament_uuid);
if (tournament_request.ok === 1) {
ipcMain.emit("controller.events.need_export", tournament_request.tournament.event_uuid);
}
}
async function create(event, event_uuid, tournament, is_import = false){
try {
let resultadoCreate = await Tournaments.create({
name: tournament.name,
tournament_type: tournament.tournament_type,
rounds_number: tournament.rounds_number,
table_start_number: (tournament.table_start_number) ? tournament.table_start_number : 1,
eventUuid: event_uuid,
ordering_sequence: (tournament.ordering_sequence) ? tournament.ordering_sequence : [],
tiebreaks: (tournament.tiebreaks) ? tournament.tiebreaks : [],
})
// console.log(resultadoCreate);
need_export(resultadoCreate.uuid);
return {ok:1,error:0,data:{uuid:resultadoCreate.uuid}};
} catch (error) {
console.log(error);
}
}
async function Import(event, event_uuid, tournament, is_import = false) {
try {
let resultadoCreate = await Tournaments.create({
uuid: (tournament.uuid) ? tournament.uuid : uuid(),
name: tournament.name,
tournament_type: tournament.tournament_type,
rounds_number: tournament.rounds_number,
table_start_number: (tournament.table_start_number) ? tournament.table_start_number : 1,
eventUuid: event_uuid,
ordering_sequence: (tournament.ordering_sequence) ? tournament.ordering_sequence : [],
tiebreaks: (tournament.tiebreaks) ? tournament.tiebreaks : [],
})
// console.log(resultadoCreate);
return { ok: 1, error: 0, data: { uuid: resultadoCreate.uuid } };
} catch (error) {
console.log(error);
}
}
async function listAll() {
try {
let tournaments = await Tournaments.findAll();
let tournaments_return = [];
let i = 0;
for(let tournament of tournaments){
let tournament_return = {
uuid: tournament.uuid,
name: tournament.name,
tournament_type: tournament.tournament_type,
rounds_number: tournament.rounds_number,
table_start_number: tournament.table_start_number,
ordering_sequence: tournament.ordering_sequence,
tiebreaks: (tournament.tiebreaks) ? tournament.tiebreaks : [],
};
tournaments_return[i++] = tournament_return;
}
return {ok:1,error:0,tournaments:tournaments_return};
} catch (error) {
console.log(error);
}
}
async function listFromEvent(event,event_uuid) {
try {
let tournaments = await Tournaments.findAll({
where: {
eventUuid: event_uuid
},
include: [
{
model: Categories,
as: "categories"
},
{
model: Events,
as: "event"
},
]
});
// console.log(tournaments);
return { ok: 1, error: 0, tournaments: await TournamentDTO.convertToExportList(tournaments)};
} catch (error) {
console.log(error);
}
}
async function get(e,uuid) {
try {
let tournament = await Tournaments.findOne({
where:{
uuid:uuid
},
include: [{
model: Categories,
as: "categories"
},
{
model: Events,
as: "event"
}
]
});
if(tournament){
return { ok: 1, error: 0, tournament: await TournamentDTO.convertToExport(tournament) };
}
return { ok: 0, error: 1, message: "Torneio não encontrado" };
} catch (error) {
console.log(error);
}
return { ok: 0, error: 1, message: "Torneio não encontrado" };
}
async function update(e,uuid,tournament){
try {
let resultado = await Tournaments.update({
name: tournament.name,
tournament_type: tournament.tournament_type,
rounds_number: tournament.rounds_number,
table_start_number: (tournament.table_start_number) ? tournament.table_start_number : 1,
ordering_sequence: tournament.ordering_sequence,
tiebreaks: (tournament.tiebreaks) ? tournament.tiebreaks : [],
},{
where:{
uuid:uuid
}
})
// console.log(resultado);
need_export(tournament.uuid);
return {ok:1,error:0};
} catch (error) {
console.log(error);
}
}
async function getPlayerPoints(e,uuid,player_uuid) {
try {
let tournament = await Tournaments.findByPk(uuid);
if(tournament){
let player_pairings_request = await PairingsController.listPlayerPairings(null,uuid,player_uuid);
if(player_pairings_request.ok === 1){
return {ok:1,error:0,points:player_pairings_request.points};
}
}
} catch (error) {
console.log(error);
}
return {ok:0,error:1,message:"Erro desconhecido"}
}
async function remove(e, uuid) {
try {
let event = await Tournaments.findByPk(uuid);
if (event) {
await Tournaments.destroy({where:{uuid:uuid}});
return { ok: 1, error: 0 };
} else {
return { ok: 0, error: 1, message: "Torneio não encontrado" };
}
} catch (error) {
console.log(error);
}
}
function getDefaultOrdering(){
[
"FIDE_RATING",
"NATIONAL_RATING",
"XADREZSUICO_RATING",
"INTERNAL_RATING",
"BORNDATE",
"ALPHABETICAL"
]
}
const Sequelize = require('sequelize');
const { app } = require('electron')
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: app.getPath('userData').concat('/database.sqlite'),
logging:false
})
module.exports = sequelize;
module.exports.convertToExport = async (category) => {
return {
uuid: category.uuid,
name: category.name,
abbr: category.abbr,
}
}
module.exports.convertToExportList = async (categories) => {
let categories_export = [];
let i = 0;
for (let category of categories) {
categories_export[i++] = await this.convertToExport(category);
}
return categories_export;
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
const Events = require('./event.basic.model');
const Tournaments = require('./tournament.basic.model');
Events.hasMany(Tournaments, {
onDelete: "CASCADE"
})
module.exports = Events;
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<router-outlet></router-outlet>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
export * from './electron/electron.service';
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment