Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Feat: move to cloud #765

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
0cec824
fix: adjust foreign key constraints for SQLite compatibility in likes…
MrPunyapal Feb 20, 2025
231dcd6
fix: drop existing likes table before recreation to ensure migration …
MrPunyapal Feb 20, 2025
96c6de0
chore: removed temp fix
MrPunyapal Feb 20, 2025
6d16c09
fix: drop existing followers table before recreation to ensure migrat…
MrPunyapal Feb 20, 2025
6c49f18
chore: remove temp fix
MrPunyapal Feb 20, 2025
84fc4c2
chore: add example testing environment configuration and update .giti…
MrPunyapal Feb 21, 2025
179d6d4
chore: update testing environment configuration in .env.testing.example
MrPunyapal Feb 21, 2025
d38eb97
fix: update foreign keys to use UUIDs in likes, bookmarks, hashtag_qu…
MrPunyapal Feb 21, 2025
66c0cfc
fix: optimize RecentQuestionsFeed query for better performance and cl…
MrPunyapal Feb 21, 2025
ea999c1
fix: enhance QuestionsFollowingFeed query for improved grouping and o…
MrPunyapal Feb 21, 2025
b0f02e2
fix: improve timestamp handling in TrendingQuestionsFeed for better d…
MrPunyapal Feb 21, 2025
b7ad2b2
fix: enhance question retrieval logic in Index component for improved…
MrPunyapal Feb 21, 2025
e6940e3
fix: optimize question retrieval queries for improved performance and…
MrPunyapal Feb 21, 2025
b1b1328
fix: enhance question retrieval logic for SQLite compatibility and im…
MrPunyapal Feb 21, 2025
43f809f
fix: add methods to retrieve database driver and timestamp functions …
MrPunyapal Feb 21, 2025
8b3113d
fix: update index creation for hashtags table and improve user settin…
MrPunyapal Feb 21, 2025
bdc275c
fix: enhance hashtag search logic for SQLite compatibility and adjust…
MrPunyapal Feb 28, 2025
f6509f4
fix: update model tests to use new assertion style and validate attri…
MrPunyapal Feb 28, 2025
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
10 changes: 10 additions & 0 deletions .env.testing.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
APP_ENV=testing
APP_KEY=

DB_CONNECTION=sqlite
DB_DATABASE=:memory:
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=pinkary_project_test
# DB_USERNAME=root
# DB_PASSWORD=
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
/storage/*.key
/vendor
.env
.env.testing
.env.backup
.env.production
.phpunit.result.cache
Expand All @@ -20,4 +21,4 @@ package-lock.json
/.vscode
/database/backups/*.sql
debugbar
.DS_Store
.DS_Store
79 changes: 57 additions & 22 deletions app/Livewire/Questions/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,28 +39,63 @@ public function render(Request $request): View
->where('pinned', true)
->first();

$questions = $user
->questionsReceived()
->select('id', 'root_id', 'parent_id')
->withExists([
'root as showRoot' => function (Builder $query) use ($user): void {
$query->where('to_id', $user->id);
},
'parent as showParent' => function (Builder $query) use ($user): void {
$query->where('to_id', $user->id);
},
])
->with('parent:id,parent_id')
->where('pinned', false)
->where('is_reported', false)
->where('is_ignored', false)
->when($user->isNot($request->user()), function (Builder|HasMany $query): void {
$query->whereNotNull('answer');
})
->havingRaw('parent_id IS NULL or showRoot = 1 or showParent = 1')
->groupBy(DB::Raw('IFNULL(root_id, id)'))
->orderByDesc(DB::raw('MAX(`updated_at`)'))
->simplePaginate($this->perPage);
if (config('database.default') === 'sqlite') {
$questions = $user
->questionsReceived()
->select('id', 'root_id', 'parent_id')
->withExists([
'root as showRoot' => function (Builder $query) use ($user): void {
$query->where('to_id', $user->id);
},
'parent as showParent' => function (Builder $query) use ($user): void {
$query->where('to_id', $user->id);
},
])
->with('parent:id,parent_id')
->where('pinned', false)
->where('is_reported', false)
->where('is_ignored', false)
->when($user->isNot($request->user()), function (Builder|HasMany $query): void {
$query->whereNotNull('answer');
})
->havingRaw('parent_id IS NULL or showRoot = 1 or showParent = 1')
->groupBy(DB::Raw('IFNULL(root_id, id)'))
->orderByDesc(DB::raw('MAX(`updated_at`)'))
->simplePaginate($this->perPage);
} else {
$questions = $user
->questionsReceived()
->select('questions.id', 'questions.root_id', 'questions.parent_id')
->joinSub(
Question::select(DB::raw('IFNULL(root_id, id) as group_id'))
->selectRaw('MAX(updated_at) as last_update')
->whereNotNull('answer')
->where('is_ignored', false)
->where('is_reported', false)
->where('to_id', $user->id)
->groupBy(DB::raw('IFNULL(root_id, id)')),
'grouped_questions',
function ($join) {
$join->on(DB::raw('IFNULL(questions.root_id, questions.id)'), '=', 'grouped_questions.group_id')
->whereRaw('questions.updated_at = grouped_questions.last_update');
}
)
->withExists([
'root as showRoot' => function (Builder $query) use ($user): void {
$query->where('to_id', $user->id);
},
'parent as showParent' => function (Builder $query) use ($user): void {
$query->where('to_id', $user->id);
},
])
->with('parent:id,parent_id')
->when($user->isNot($request->user()), function (Builder|HasMany $query): void {
$query->whereNotNull('answer');
})
->havingRaw('parent_id IS NULL or showRoot = 1 or showParent = 1')
->orderByDesc('grouped_questions.last_update')
->simplePaginate($this->perPage);
}

return view('livewire.questions.index', [
'user' => $user,
Expand Down
36 changes: 33 additions & 3 deletions app/Queries/Feeds/QuestionsFollowingFeed.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,39 @@ public function builder(): Builder
});
};

if (config('database.default') === 'sqlite') {
return Question::query()
->select('id', 'root_id', 'parent_id')
->withExists([
'root as showRoot' => $followQueryClosure,
'parent as showParent' => $followQueryClosure,
])
->with('root:id,to_id', 'root.to:id,username', 'parent:id,parent_id')
->whereNotNull('answer')
->where('is_reported', false)
->where('is_ignored', false)
->where($followQueryClosure)
->havingRaw('parent_id IS NULL or showRoot = 1 or showParent = 1')
->groupBy(DB::Raw('IFNULL(root_id, id)'))
->orderByDesc(DB::raw('MAX(`updated_at`)'));
}

return Question::query()
->select('id', 'root_id', 'parent_id')
->joinSub(
Question::select(DB::raw('IFNULL(root_id, id) as group_id'))
->selectRaw('MAX(updated_at) as last_update')
->whereNotNull('answer')
->where('is_ignored', false)
->where($followQueryClosure)
->where('is_reported', false)
->groupBy(DB::raw('IFNULL(root_id, id)')),
'grouped_questions',
function ($join) {
$join->on(DB::raw('IFNULL(questions.root_id, questions.id)'), '=', 'grouped_questions.group_id')
->whereRaw('questions.updated_at = grouped_questions.last_update');
}
)
->select('questions.id', 'questions.root_id', 'questions.parent_id')
->withExists([
'root as showRoot' => $followQueryClosure,
'parent as showParent' => $followQueryClosure,
Expand All @@ -48,7 +79,6 @@ public function builder(): Builder
->where('is_ignored', false)
->where($followQueryClosure)
->havingRaw('parent_id IS NULL or showRoot = 1 or showParent = 1')
->groupBy(DB::Raw('IFNULL(root_id, id)'))
->orderByDesc(DB::raw('MAX(`updated_at`)'));
->orderByDesc('grouped_questions.last_update');
}
}
41 changes: 37 additions & 4 deletions app/Queries/Feeds/RecentQuestionsFeed.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,30 @@ public function __construct(
*/
public function builder(): Builder
{
if (config('database.default') === 'sqlite') {
return Question::query()
->select('id')
->whereNotNull('answer')
->where('is_ignored', false)
->where('is_reported', false)
->when($this->hashtag, function (Builder $query): void {
$query->whereHas('hashtags', function (Builder $query): void {
$query
// using 'like' for this query (with no wildcards) will
// result in a case-insensitive lookup from sqlite,
// which is what we want.
->where('name', 'like', $this->hashtag);
})->orderByDesc('updated_at');
}, function (Builder $query): void {
$query->addSelect('root_id', 'parent_id')
->with('root.to:username,id', 'root:id,to_id', 'parent:id,parent_id')
->groupBy(DB::Raw('IFNULL(root_id, id)'))
->orderByDesc(DB::raw('MAX(`updated_at`)'));
});
}

return Question::query()
->select('id')
->select('questions.id', 'questions.root_id', 'questions.parent_id')
->whereNotNull('answer')
->where('is_ignored', false)
->where('is_reported', false)
Expand All @@ -38,10 +60,21 @@ public function builder(): Builder
->where('name', 'like', $this->hashtag);
})->orderByDesc('updated_at');
}, function (Builder $query): void {
$query->addSelect('root_id', 'parent_id')
$query->joinSub(
Question::select(DB::raw('IFNULL(root_id, id) as group_id'))
->selectRaw('MAX(updated_at) as last_update')
->whereNotNull('answer')
->where('is_ignored', false)
->where('is_reported', false)
->groupBy(DB::raw('IFNULL(root_id, id)')),
'grouped_questions',
function ($join) {
$join->on(DB::raw('IFNULL(questions.root_id, questions.id)'), '=', 'grouped_questions.group_id')
->whereRaw('questions.updated_at = grouped_questions.last_update');
}
)
->with('root.to:username,id', 'root:id,to_id', 'parent:id,parent_id')
->groupBy(DB::Raw('IFNULL(root_id, id)'))
->orderByDesc(DB::raw('MAX(`updated_at`)'));
->orderByDesc('grouped_questions.last_update');
});
}
}
35 changes: 34 additions & 1 deletion app/Queries/Feeds/TrendingQuestionsFeed.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,52 @@ public function builder(): Builder
$timeBias = self::TIME_BIAS;
$maxDaysSincePosted = self::MAX_DAYS_SINCE_POSTED;

$currentTimestamp = $this->getTimestampFunction();
$answerTimestamp = $this->getTimestampWithColumnFunction('answer_created_at');

return Question::query()
->select('id')
->from(
Question::query()
->withCount('likes', 'children')
->orderByRaw(<<<SQL
(((likes_count * {$likesBias} + 1.0) * (children_count * {$commentsBias} + 1.0))
/ (strftime('%s') - strftime('%s', answer_created_at) + {$timeBias} + 1.0)) desc
/ ({$currentTimestamp} - {$answerTimestamp} + {$timeBias} + 1.0)) desc
SQL)
->where('is_reported', false)
->where('is_ignored', false)
->where('answer_created_at', '>=', now()->subDays($maxDaysSincePosted)),
'trending_questions'
);
}

/**
* Get the database driver.
*/
private function getDatabaseDriver(): string
{
return config('database.default');
}

/**
* Get the timestamp function for the database driver.
*/
private function getTimestampFunction(): string
{
return match ($this->getDatabaseDriver()) {
'sqlite' => "strftime('%s')",
default => 'UNIX_TIMESTAMP()',
};
}

/**
* Get the timestamp function for the database driver with a column.
*/
private function getTimestampWithColumnFunction(string $column): string
{
return match ($this->getDatabaseDriver()) {
'sqlite' => "strftime('%s', {$column})",
default => "UNIX_TIMESTAMP({$column})",
};
}
}
11 changes: 10 additions & 1 deletion app/Services/Autocomplete/Types/Hashtags.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

use App\Models\Hashtag;
use App\Services\Autocomplete\Result;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

final readonly class Hashtags extends Type
{
Expand Down Expand Up @@ -37,7 +39,14 @@ public function search(?string $query): Collection
{
return Hashtag::query()
->withCount('questions')
->where('name', 'like', "$query%")
->when(
config('database.default') === 'sqlite',
function (Builder $q) use ($query): void {
$q->where('name', 'like', "$query%");
}, function (Builder $q) use ($query): void {
$q->where(DB::raw('LOWER(name)'), 'like', mb_strtolower("$query%"));
}
)
->orderByDesc('questions_count')
->limit(50)
->get()
Expand Down
8 changes: 7 additions & 1 deletion database/migrations/2024_02_25_144346_create_likes_table.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ public function up(): void
Schema::create('likes', function (Blueprint $table): void {
$table->id();
$table->foreignIdFor(User::class)->constrained()->cascadeOnDelete();
$table->foreignIdFor(Question::class)->constrained()->cascadeOnDelete();

if (config('database.default') === 'sqlite') {
$table->foreignIdFor(Question::class)->constrained()->cascadeOnDelete();
} else {
$table->unsignedBigInteger('question_id');
$table->foreign('question_id')->references('id')->on('questions')->cascadeOnDelete();
}

$table->unique(['user_id', 'question_id']);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@
*/
public function up(): void
{
Schema::drop('questions');
if (config('database.default') !== 'sqlite') {
Schema::table('likes', function (Blueprint $table) {
$table->dropForeign(['question_id']);
});
}

Schema::dropIfExists('questions');

Schema::create('questions', function (Blueprint $table): void {
$table->uuid('id')->primary();
Expand All @@ -29,5 +35,12 @@ public function up(): void

$table->timestamps();
});

if (config('database.default') !== 'sqlite') {
Schema::table('likes', function (Blueprint $table) {
$table->uuid('question_id')->change();
$table->foreign('question_id')->references('id')->on('questions')->cascadeOnDelete();
});
}
}
};
12 changes: 10 additions & 2 deletions database/migrations/2024_08_07_203106_create_hashtags_table.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,18 @@ public function up(): void
{
Schema::create('hashtags', function (Blueprint $table): void {
$table->id();
$table->string('name')->unique();

if (config('database.default') === 'sqlite') {
$table->string('name')->unique();
} else {
$table->string('name')->unique()->collation('utf8mb4_bin');
}

$table->timestamps();

$table->rawIndex('name collate nocase', 'name_collate_nocase');
if (config('database.default') === 'sqlite') {
$table->rawIndex('name collate nocase', 'name_collate_nocase');
}
});

Schema::create('hashtag_question', function (Blueprint $table): void {
Expand Down
Loading