Foosball Api, A REST api written in C# .NET CORE 6 WEB API 3.1 and uses Postgresql database.
We use Dapper for our ORM.
Run the project with the following command
dotnet runThe project run on port 5297 --> https://localhost:5297/swagger/index.html
The project runs on docker on port 8080
Secrets are added to secrets.json file with dotnet user-secrets
To run the project. The following variables are needed
{
"SmtpUser": "",
"SmtpPort": "",
"SmtpPass": "",
"SmtpHost": "",
"SmtpEmailFrom": "",
"JwtSecret": "",
"DatoCmsBearer": "",
"FoosballDbDev": "",
"FoosballDbProd": ""
}.net core [Dapper] (https://github.com/DapperLib/Dapper) postgresql
To come.
Remember to format the code using dotnet-format
First install dotnet-format globally on your machine:
dotnet tool install -g dotnet-formatThen format the code using:
dotnet formatwhen doing patch requests. Have the body something like this:
[
{
"op": "replace",
"path": "/Name",
"value": "Some new name"
}
]A couple of Postgres triggers are used in the system. Here are the complete up to date list of all the triggers.
When a freehand match is changed
CREATE OR REPLACE FUNCTION notify_score_update() RETURNS trigger AS $$
BEGIN
IF NEW.game_finished = false THEN
PERFORM pg_notify(
'score_update',
json_build_object(
'match_id', NEW.id,
'player_one_id', NEW.player_one_id,
'player_two_id', NEW.player_two_id,
'player_one_score', NEW.player_one_score,
'player_two_score', NEW.player_two_score,
'start_time', NEW.start_time,
'end_time', NEW.end_time,
'up_to', NEW.up_to,
'game_finished', NEW.game_finished,
'game_paused', NEW.game_paused,
'organisation_id', NEW.organisation_id,
'player_one', (
SELECT json_build_object(
'id', u1.id,
'first_name', COALESCE(u1.first_name, 'Unknown'),
'last_name', COALESCE(u1.last_name, 'Unknown'),
'photo_url', COALESCE(u1.photo_url, 'default_image_url')
)
FROM users u1
WHERE u1.id = NEW.player_one_id
),
'player_two', (
SELECT json_build_object(
'id', u2.id,
'first_name', COALESCE(u2.first_name, 'Unknown'),
'last_name', COALESCE(u2.last_name, 'Unknown'),
'photo_url', COALESCE(u2.photo_url, 'default_image_url')
)
FROM users u2
WHERE u2.id = NEW.player_two_id
)
)::text
);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER score_update_trigger
AFTER UPDATE OF player_one_score, player_two_score ON freehand_matches
FOR EACH ROW
WHEN (OLD.player_one_score IS DISTINCT FROM NEW.player_one_score OR
OLD.player_two_score IS DISTINCT FROM NEW.player_two_score)
EXECUTE FUNCTION notify_score_update();
CREATE TRIGGER score_insert_trigger
AFTER INSERT ON freehand_matches
FOR EACH ROW
EXECUTE FUNCTION notify_score_insert();
CREATE OR REPLACE FUNCTION notify_score_insert() RETURNS trigger AS $$
BEGIN
IF NEW.game_finished = false THEN
PERFORM pg_notify(
'score_update',
json_build_object(
'match_id', NEW.id,
'player_one_id', NEW.player_one_id,
'player_two_id', NEW.player_two_id,
'player_one_score', NEW.player_one_score,
'player_two_score', NEW.player_two_score,
'start_time', NEW.start_time,
'end_time', NEW.end_time,
'up_to', NEW.up_to,
'game_finished', NEW.game_finished,
'game_paused', NEW.game_paused,
'organisation_id', NEW.organisation_id,
'player_one', (
SELECT json_build_object(
'id', u1.id,
'first_name', COALESCE(u1.first_name, 'Unknown'),
'last_name', COALESCE(u1.last_name, 'Unknown'),
'photo_url', COALESCE(u1.photo_url, 'default_image_url')
)
FROM users u1
WHERE u1.id = NEW.player_one_id
),
'player_two', (
SELECT json_build_object(
'id', u2.id,
'first_name', COALESCE(u2.first_name, 'Unknown'),
'last_name', COALESCE(u2.last_name, 'Unknown'),
'photo_url', COALESCE(u2.photo_url, 'default_image_url')
)
FROM users u2
WHERE u2.id = NEW.player_two_id
)
)::text
);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION notify_double_score_update() RETURNS trigger AS $$
BEGIN
IF NEW.game_finished = false THEN
PERFORM pg_notify(
'double_score_update',
json_build_object(
'match_id', NEW.id,
'team_a_player_one_id', NEW.player_one_team_a,
'team_a_player_two_id', NEW.player_two_team_a,
'team_b_player_one_id', NEW.player_one_team_b,
'team_b_player_two_id', NEW.player_two_team_b,
'team_a_score', NEW.team_a_score,
'team_b_score', NEW.team_b_score,
'start_time', NEW.start_time,
'end_time', NEW.end_time,
'up_to', NEW.up_to,
'game_finished', NEW.game_finished,
'game_paused', NEW.game_paused,
'organisation_id', NEW.organisation_id,
'team_a_player_one', (
SELECT json_build_object(
'id', u1.id,
'first_name', COALESCE(u1.first_name, 'Unknown'),
'last_name', COALESCE(u1.last_name, 'Unknown'),
'photo_url', COALESCE(u1.photo_url, 'default_image_url')
)
FROM users u1
WHERE u1.id = NEW.player_one_team_a
),
'team_a_player_two', (
SELECT json_build_object(
'id', u2.id,
'first_name', COALESCE(u2.first_name, 'Unknown'),
'last_name', COALESCE(u2.last_name, 'Unknown'),
'photo_url', COALESCE(u2.photo_url, 'default_image_url')
)
FROM users u2
WHERE u2.id = NEW.player_two_team_a
),
'team_b_player_one', (
SELECT json_build_object(
'id', u3.id,
'first_name', COALESCE(u3.first_name, 'Unknown'),
'last_name', COALESCE(u3.last_name, 'Unknown'),
'photo_url', COALESCE(u3.photo_url, 'default_image_url')
)
FROM users u3
WHERE u3.id = NEW.player_one_team_b
),
'team_b_player_two', (
SELECT json_build_object(
'id', u4.id,
'first_name', COALESCE(u4.first_name, 'Unknown'),
'last_name', COALESCE(u4.last_name, 'Unknown'),
'photo_url', COALESCE(u4.photo_url, 'default_image_url')
)
FROM users u4
WHERE u4.id = NEW.player_two_team_b
),
'last_goal', (
SELECT json_build_object(
'scored_by_user_id', g.scored_by_user_id,
'scorer_team_score', g.scorer_team_score,
'opponent_team_score', g.opponent_team_score,
'time_of_goal', g.time_of_goal,
'winner_goal', g.winner_goal,
'scorer', (
SELECT json_build_object(
'id', u5.id,
'first_name', COALESCE(u5.first_name, 'Unknown'),
'last_name', COALESCE(u5.last_name, 'Unknown'),
'photo_url', COALESCE(u5.photo_url, 'default_image_url')
)
FROM users u5
WHERE u5.id = g.scored_by_user_id
)
)
FROM freehand_double_goals g
WHERE g.double_match_id = NEW.id
ORDER BY g.time_of_goal DESC
LIMIT 1
)
)::text
);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION notify_double_score_insert() RETURNS trigger AS $$
BEGIN
RAISE NOTICE 'Trigger Fired on Insert: %', NEW.id;
IF NEW.game_finished = false THEN
RAISE NOTICE 'IF STATEMENT is here %', NEW.id;
PERFORM pg_notify(
'double_score_update',
json_build_object(
'match_id', NEW.id,
'team_a_player_one_id', NEW.player_one_team_a,
'team_a_player_two_id', NEW.player_two_team_a,
'team_b_player_one_id', NEW.player_one_team_b,
'team_b_player_two_id', NEW.player_two_team_b,
'team_a_score', NEW.team_a_score,
'team_b_score', NEW.team_b_score,
'start_time', NEW.start_time,
'end_time', NEW.end_time,
'up_to', NEW.up_to,
'game_finished', NEW.game_finished,
'game_paused', NEW.game_paused,
'organisation_id', NEW.organisation_id,
'team_a_player_one', (
SELECT json_build_object(
'id', u1.id,
'first_name', COALESCE(u1.first_name, 'Unknown'),
'last_name', COALESCE(u1.last_name, 'Unknown'),
'photo_url', COALESCE(u1.photo_url, 'default_image_url')
)
FROM users u1
WHERE u1.id = NEW.player_one_team_a
),
'team_a_player_two', (
SELECT json_build_object(
'id', u2.id,
'first_name', COALESCE(u2.first_name, 'Unknown'),
'last_name', COALESCE(u2.last_name, 'Unknown'),
'photo_url', COALESCE(u2.photo_url, 'default_image_url')
)
FROM users u2
WHERE u2.id = NEW.player_two_team_a
),
'team_b_player_one', (
SELECT json_build_object(
'id', u3.id,
'first_name', COALESCE(u3.first_name, 'Unknown'),
'last_name', COALESCE(u3.last_name, 'Unknown'),
'photo_url', COALESCE(u3.photo_url, 'default_image_url')
)
FROM users u3
WHERE u3.id = NEW.player_one_team_b
),
'team_b_player_two', (
SELECT json_build_object(
'id', u4.id,
'first_name', COALESCE(u4.first_name, 'Unknown'),
'last_name', COALESCE(u4.last_name, 'Unknown'),
'photo_url', COALESCE(u4.photo_url, 'default_image_url')
)
FROM users u4
WHERE u4.id = NEW.player_two_team_b
),
'last_goal', (
SELECT json_build_object(
'scored_by_user_id', g.scored_by_user_id,
'scorer_team_score', g.scorer_team_score,
'opponent_team_score', g.opponent_team_score,
'time_of_goal', g.time_of_goal,
'winner_goal', g.winner_goal,
'scorer', (
SELECT json_build_object(
'id', u5.id,
'first_name', COALESCE(u5.first_name, 'Unknown'),
'last_name', COALESCE(u5.last_name, 'Unknown'),
'photo_url', COALESCE(u5.photo_url, 'default_image_url')
)
FROM users u5
WHERE u5.id = g.scored_by_user_id
)
)
FROM freehand_double_goals g
WHERE g.double_match_id = NEW.id
ORDER BY g.time_of_goal DESC
LIMIT 1
)
)::text
);
RAISE NOTICE 'end is here: Player One Team A ID: %, Score: %', NEW.player_one_team_a, NEW.team_a_score;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER double_score_update_trigger
AFTER UPDATE OF team_a_score, team_b_score ON freehand_double_matches
FOR EACH ROW
WHEN (OLD.team_a_score IS DISTINCT FROM NEW.team_a_score OR
OLD.team_b_score IS DISTINCT FROM NEW.team_b_score)
EXECUTE FUNCTION notify_double_score_update();
CREATE TRIGGER double_score_insert_trigger
AFTER INSERT ON freehand_double_matches
FOR EACH ROW
EXECUTE FUNCTION notify_double_score_insert();
CREATE OR REPLACE FUNCTION notify_single_league_score_update() RETURNS trigger AS $$
DECLARE
last_goal json;
BEGIN
-- Check if the match is ongoing and not ended
IF NEW.match_ended = false AND NEW.match_started = true THEN
-- Get the most recent goal information for this match (if any)
SELECT json_build_object(
'scored_by_user_id', g.scored_by_user_id,
'scorer_team_score', g.scorer_score,
'opponent_team_score', g.opponent_score,
'time_of_goal', g.time_of_goal,
'winner_goal', g.winner_goal,
'scorer', (
SELECT json_build_object(
'id', u1.id,
'first_name', COALESCE(u1.first_name, 'Unknown'),
'last_name', COALESCE(u1.last_name, 'Unknown'),
'photo_url', COALESCE(u1.photo_url, 'default_image_url')
)
FROM users u1
WHERE u1.id = g.scored_by_user_id
)
) INTO last_goal
FROM single_league_goals g
WHERE g.match_id = NEW.id
ORDER BY g.time_of_goal DESC
LIMIT 1;
-- Notify the listeners with match and goal information
PERFORM pg_notify(
'single_league_score_update',
json_build_object(
'match_id', NEW.id,
'player_one_id', NEW.player_one,
'player_two_id', NEW.player_two,
'player_one_score', NEW.player_one_score,
'player_two_score', NEW.player_two_score,
'start_time', NEW.start_time,
'end_time', NEW.end_time,
'up_to', NEW.up_to,
'match_ended', NEW.match_ended,
'match_paused', NEW.match_paused,
'organisation_id', NEW.organisation_id,
'LastGoal', last_goal,
'player_one', (
SELECT json_build_object(
'id', u1.id,
'first_name', COALESCE(u1.first_name, 'Unknown'),
'last_name', COALESCE(u1.last_name, 'Unknown'),
'photo_url', COALESCE(u1.photo_url, 'default_image_url')
)
FROM users u1
WHERE u1.id = NEW.player_one
),
'player_two', (
SELECT json_build_object(
'id', u2.id,
'first_name', COALESCE(u2.first_name, 'Unknown'),
'last_name', COALESCE(u2.last_name, 'Unknown'),
'photo_url', COALESCE(u2.photo_url, 'default_image_url')
)
FROM users u2
WHERE u2.id = NEW.player_two
)
)::text
);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER notify_single_league_score_update_trigger
AFTER UPDATE ON single_league_matches
FOR EACH ROW
WHEN (NEW.match_started = true AND NEW.match_ended = false)
EXECUTE FUNCTION notify_single_league_score_update();
CREATE OR REPLACE FUNCTION notify_double_league_score_update() RETURNS trigger AS $$
DECLARE
last_goal json;
league_up_to int;
league_organisation_id int;
BEGIN
RAISE NOTICE 'Trigger fired for match_id %', NEW.id;
-- Check if the match is ongoing and not ended
IF NEW.match_ended = false AND NEW.match_started = true THEN
RAISE NOTICE 'Match is ongoing for match_id %', NEW.id;
-- Fetch the `up_to` and `organisation_id` values from the `leagues` table
SELECT l.up_to, l.organisation_id INTO league_up_to, league_organisation_id
FROM leagues l
WHERE l.id = NEW.league_id;
RAISE NOTICE 'Fetched league data for league_id %', NEW.league_id;
-- Get the most recent goal information for this match (if any)
SELECT json_build_object(
'scored_by_user_id', g.scored_by_user_id,
'scorer_team_score', g.scorer_score,
'opponent_team_score', g.opponent_score,
'time_of_goal', g.time_of_goal,
'winner_goal', g.winner_goal,
'scorer', (
SELECT json_build_object(
'id', u1.id,
'first_name', COALESCE(u1.first_name, 'Unknown'),
'last_name', COALESCE(u1.last_name, 'Unknown'),
'photo_url', COALESCE(u1.photo_url, 'default_image_url')
)
FROM users u1
WHERE u1.id = g.scored_by_user_id
)
) INTO last_goal
FROM double_league_goals g
WHERE g.match_id = NEW.id
ORDER BY g.time_of_goal DESC
LIMIT 1;
RAISE NOTICE 'Fetched last goal for match_id %', NEW.id;
-- Notify the listeners with match and goal information
PERFORM pg_notify(
'double_league_score_update',
json_build_object(
'match_id', NEW.id,
'team_one_id', NEW.team_one_id,
'team_two_id', NEW.team_two_id,
'team_one_score', NEW.team_one_score,
'team_two_score', NEW.team_two_score,
'start_time', NEW.start_time,
'end_time', NEW.end_time,
'up_to', league_up_to,
'match_ended', NEW.match_ended,
'match_paused', NEW.match_paused,
'organisation_id', league_organisation_id,
'LastGoal', last_goal,
'team_one_players', (
SELECT json_agg(json_build_object(
'id', u.id,
'first_name', COALESCE(u.first_name, 'Unknown'),
'last_name', COALESCE(u.last_name, 'Unknown'),
'photo_url', COALESCE(u.photo_url, 'default_image_url')
))
FROM double_league_players dlp
JOIN users u ON dlp.user_id = u.id
WHERE dlp.double_league_team_id = NEW.team_one_id
),
'team_two_players', (
SELECT json_agg(json_build_object(
'id', u.id,
'first_name', COALESCE(u.first_name, 'Unknown'),
'last_name', COALESCE(u.last_name, 'Unknown'),
'photo_url', COALESCE(u.photo_url, 'default_image_url')
))
FROM double_league_players dlp
JOIN users u ON dlp.user_id = u.id
WHERE dlp.double_league_team_id = NEW.team_two_id
)
)::text
);
RAISE NOTICE 'THE END IS HERE %', NEW.id;
ELSE
RAISE NOTICE 'Match is not ongoing or already ended for match_id %', NEW.id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER notify_double_league_score_update_trigger
AFTER UPDATE ON double_league_matches
FOR EACH ROW
WHEN (NEW.match_started = true AND NEW.match_ended = false)
EXECUTE FUNCTION notify_double_league_score_update();
CREATE TRIGGER notify_double_league_insert_trigger
AFTER INSERT ON double_league_matches
FOR EACH ROW
EXECUTE FUNCTION notify_double_league_score_update();
Foosball © 2021+, Mossfellsbær City. Released under the [MIT License].
Authored and maintained by Daniel Freyr Sigurdsson. With help from [contributors].
docker build -t danielfoosball/foosballapi:1 .
docker run -p 8080:80 danielfoosball/foosballapi:1 sudo docker run -p 8080:80 --env-file .env danielfoosball/foosballapi:1
sudo docker run -p 8080:8080
--env-file .env
--add-host=host.docker.internal:host-gateway
danielfoosball/foosballapi:1
docker images
you cant access swagger html page from docker, beacause its in production and swagger only works in development
just kill the terminal in vscode should work
pg_dump -U postgres -d Foosball_DB -F p -f backup.sql
These endpoints should be implemented
| Controller | method | Name | PATH | Status |
|---|---|---|---|---|
| Auth | POST | Login | Auth/login | x |
| Auth | POST | register | Auth/register | x |
| Auth | POST | verify-email | Auth/verify-email | x |
| Auth | POST | forgot-password | Auth/forgot-password | |
| Auth | POST | reset-password | Auth/reset-password | |
| Cms | POST | hardcoded-string | Cms/hardcoded-strings | x |
| DoubleLeagueGoals | GET | Get match by id | DoubleLeagueGoals/match/{matchId} | x |
| DoubleLeagueGoals | GET | Get goal by goalId | DoubleLeagueGoals/{goalId} | x |
| DoubleLeagueGoals | DELETE | Delete dlg by ID | DoubleLeagueGoals/{goalId} | x |
| DoubleLeagueGoals | POST | Create new goal | DoubleLeagueGoals | x |
| DoubleLeagueMatches | GET | Get all Dl matches | DoubleLeagueMatches | x |
| DoubleLeagueMatches | PATCH | Update dl match | DoubleLeagueMatches | x |
| DoubleLeagueMatches | GET | Get match by id | DoubleLeagueMatches/match/{matchId} | x |
| DoubleLeagueMatches | PUT | reset-match | DoubleLeagueMatches/reset-match | x |
| DoubleLeaguePlayers | GET | Gel all league pl | DoubleLeaguePlayers/{leagueId} | x |
| DoubleLeaguePlayers | GET | Get player by id | DoubleLeaguePlayers/player/{id} | x |
| DoubleLeagueTeams | GET | Get teams by l. id | DoubleLeagueTeams/{leagueId} | x |
| DoubleLeagueTeams | GET | Get team by id | DoubleLeagueTeams/team/{id} | x |
| DoubleLeagueTeams | POST | Create new league | DoubleLeagueTeams/{leagueId}/{teamId} | x |
| DoubleLeagueTeams | DELETE | Delete league | DoubleLeagueTeams/{leagueId}/{teamId} | x |
| FreehandDoubleGoals | GET | Get dlga by matchId | FreehandDoubleGoals/goals/{matchId} | x |
| FreehandDoubleGoals | GET | get dlb by id | FreehandDoubleGoals/{goalId} | x |
| FreehandDoubleGoals | POST | Create dlg goal | FreehandDoubleGoals | x |
| FreehandDoubleGoals | PATCH | UPDATE dlg goal | FreehandDoubleGoals | x |
| FreehandDoubleGoals | DELETE | Delete dlg goal | FreehandDoubleGoals/{matchId}/{goalId} | x |
| FreehandDoubleMatches | GET | Get fh matches | FreehandDoubleMatches | x |
| FreehandDoubleMatches | POST | Create fh match | FreehandDoubleMatches | x |
| FreehandDoubleMatches | PATCH | Update fdm | FreehandDoubleMatches | x |
| FreehandDoubleMatches | GET | Get fdm by matchId | FreehandDoubleMatches/{matchId} | x |
| FreehandDoubleMatches | DELETE | Delete fdm | FreehandDoubleMatches/{matchId} | x |
| FreehandGoals | GET | Get goals by mId | FreehandGoals/goals/{matchId} | x |
| FreehandGoals | GET | Get goal by gId | FreehandGoals/{goalId} | x |
| FreehandGoals | POST | Create f goal | FreehandGoals | x |
| FreehandGoals | PATCH | Update f goal | FreehandGoals | x |
| FreehandGoals | DELETE | Delete f goal | FreehandGoals/{matchId}/{goalId} | x |
| FreehandMatches | GET | Get f matches | FreehandMatches | x |
| FreehandMatches | POST | Create f match | FreehandMatches | x |
| FreehandMatches | PATCH | Update f match | FreehandMatches | x |
| FreehandMatches | GET | Get f match by mID | FreehandMatches/{matchId} | x |
| FreehandMatches | DELETE | Delete f match | FreehandMatches/{matchId} | x |
| Leagues | GET | Get leagus by org. | Leagues/organisation | x |
| Leagues | GET | Get league by id | Leagues/{id} | x |
| Leagues | PATCH | UPDATE league by id | Leagues/{id} | x |
| Leagues | GET | Get league players | Leagues/league-players | x |
| Leagues | POST | Create new league | Leagues | x |
| Leagues | DELETE | Delete league by id | Leagues/{leagueId} | x |
| Leagues | GET | Get leag. standings | Leagues/single-league/standings | x |
| Leagues | GET | Get dLeague stand. | Leagues/double-league/standings | x |
| Organisations | POST | Create organisation | Organisations | x |
| Organisations | GET | Get org by id | Organisations/{id} | x |
| Organisations | PATCH | Update org by id | Organisations/{id} | x |
| Organisations | DELETE | Delete org by id | Organisations/{id} | x |
| Organisations | GET | Get orgs by user | Organisations/user | x |
| SingleLeagueGoals | GET | Get sl goals | SingleLeagueGoals | x |
| SingleLeagueGoals | POST | Create sl goal | SingleLeagueGoals | x |
| SingleLeagueGoals | GET | Get sl goal by gId | SingleLeagueGoals/{goalId} | x |
| SingleLeagueGoals | DELETE | Delete slgoal by id | SingleLeagueGoals/{goalId} | x |
| SingleLeagueMatches | GET | Get sl matches | SingleLeagueMatches | x |
| SingleLeagueMatches | PATCH | Update sl match | SingleLeagueMatches | x |
| SingleLeagueMatches | GET | Get sl match | SingleLeagueMatches/{matchId} | x |
| SingleLeagueMatches | PUT | Reset sl match | SingleLeagueMatches/reset-match | x |
| Users | GET | Get users | Users | x |
| Users | GET | Get user by id | Users/{id} | x |
| Users | PATCH | Update user by id | Users/{id} | x |
| Users | DELETE | Delete user by id | Users/{id} | x |
| Users | GET | Get user stats | Users/stats | x |
| Users | GET | Get last 10 matches | Users/last-ten-matches | x |
| Users | GET | Get history | Users/history | x |
dotnet dev-certs https --clean dotnet dev-certs https --trust
ALTER TABLE organisations ADD organisation_code TEXT
ALTER TABLE organisations ADD CONSTRAINT orgnisation_code_unique UNIQUE (organisation_code);
CREATE UNIQUE INDEX idx_organisation_code ON organisations(organisation_code);
ALTER TABLE organisation_list ADD is_admin boolean
alter table organisation_list add column "is_deleted" BOOLEAN DEFAULT FALSE
ALTER TABLE users ADD refresh_token varchar, ADD refresh_token_expiry_time timestamp;
ALTER TABLE users ADD refresh_token_web varchar, ADD refresh_token_web_expiry_time timestamp;
CREATE TABLE old_refresh_tokens ( id SERIAL PRIMARY KEY, refresh_token CHARACTER VARYING NOT NULL, refresh_token_expiry_time TIMESTAMP WITHOUT TIME ZONE NOT NULL, fk_user_id INTEGER REFERENCES users(id), fk_organisation_id INTEGER REFERENCES organisations(id) );
ALTER TABLE old_refresh_tokens ADD inserted_at timestamp
ALTER TABLE verifications ADD change_password_token text;
ALTER TABLE verifications ADD change_password_token_expires timestamp without time zone;
ALTER TABLE verifications ADD COLUMN change_password_verification_token text
ALTER TABLE organisations ADD COLUMN slack_webhook_url text
ollama pull phi3:mini
ALTER TABLE organisations ADD COLUMN discord_webhook_url TEXT;
ALTER TABLE organisations ADD COLUMN microsoft_teams_webhook_url TEXT;
ALTER TABLE users ADD COLUMN auth_provider text DEFAULT 'LOCAL' ADD COLUMN google_id text;