From 5ca25ed9a71193fbcdeec086dce43df979539066 Mon Sep 17 00:00:00 2001 From: Hamza Date: Fri, 1 Nov 2019 17:28:56 +0500 Subject: [PATCH 01/75] search: it works(tm) --- build.gradle.kts | 2 + docker-compose.yml | 9 +++++ src/blogify/backend/Application.kt | 6 +-- .../backend/resources/search/Search.kt | 37 +++++++++++++++++++ .../backend/routes/articles/ArticleRoutes.kt | 28 ++++++++++++-- 5 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 src/blogify/backend/resources/search/Search.kt diff --git a/build.gradle.kts b/build.gradle.kts index 8912bee6..01fa241b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -69,6 +69,8 @@ dependencies { compile("com.github.kittinunf.result:result:2.2.0") compile("com.github.kittinunf.result:result-coroutines:2.2.0") + compile("io.ktor:ktor-client-cio:$ktor_version") + // JJWT compile("io.jsonwebtoken:jjwt-api:0.10.7") diff --git a/docker-compose.yml b/docker-compose.yml index 47508480..b675b0f0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,7 @@ services: - '5005:5005' depends_on: - db + - es volumes: - 'static-data:/var/static/' db: @@ -15,6 +16,14 @@ services: - '5432:5432' volumes: - 'db-data:/var/lib/postgresql/data' + es: + image: 'docker.elastic.co/elasticsearch/elasticsearch:7.4.0' + ports: + - '9200:9200' + - '9300:9300' + environment: + - 'discovery.type=single-node' + - 'node.name=es' volumes: db-data: diff --git a/src/blogify/backend/Application.kt b/src/blogify/backend/Application.kt index 1f054b5f..295e366f 100644 --- a/src/blogify/backend/Application.kt +++ b/src/blogify/backend/Application.kt @@ -40,10 +40,8 @@ import io.ktor.routing.routing import org.jetbrains.exposed.sql.SchemaUtils import kotlinx.coroutines.runBlocking -import org.jetbrains.exposed.sql.update import org.slf4j.event.Level -import java.util.UUID const val version = "PRX4" @@ -57,7 +55,9 @@ const val asciiLogo = """ ---- Version $version - Development build - """ -fun main(args: Array): Unit = io.ktor.server.netty.EngineMain.main(args) +fun main(args: Array) { + io.ktor.server.netty.EngineMain.main(args) +} @Suppress("unused") // Referenced in application.conf @kotlin.jvm.JvmOverloads diff --git a/src/blogify/backend/resources/search/Search.kt b/src/blogify/backend/resources/search/Search.kt new file mode 100644 index 00000000..645da02a --- /dev/null +++ b/src/blogify/backend/resources/search/Search.kt @@ -0,0 +1,37 @@ +package blogify.backend.resources.search + +import blogify.backend.resources.models.Resource + +data class Search( + val _shards: Shards, + val hits: Hits, + val timed_out: Boolean, + val took: Int +) { + data class Shards( + val failed: Int, + val skipped: Int, + val successful: Int, + val total: Int + ) + + data class Hits( + val hits: List>, + val max_score: Double, + val total: Total + ) + + data class Hit( + val _id: String, + val _index: String, + val _score: Double, + val _source: S, + val _type: String + ) + + data class Total( + val relation: String, + val value: Int + ) + +} \ No newline at end of file diff --git a/src/blogify/backend/routes/articles/ArticleRoutes.kt b/src/blogify/backend/routes/articles/ArticleRoutes.kt index 72c1d7b8..64093195 100644 --- a/src/blogify/backend/routes/articles/ArticleRoutes.kt +++ b/src/blogify/backend/routes/articles/ArticleRoutes.kt @@ -9,17 +9,20 @@ import blogify.backend.database.Articles import blogify.backend.database.Users import blogify.backend.resources.Article import blogify.backend.resources.models.eqr +import blogify.backend.resources.search.Search import blogify.backend.resources.slicing.sanitize import blogify.backend.resources.slicing.slice import blogify.backend.routes.handling.* import blogify.backend.services.UserService import blogify.backend.services.articles.ArticleService import blogify.backend.services.models.Service +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import io.ktor.client.HttpClient +import io.ktor.client.request.get import io.ktor.response.respond -import org.jetbrains.exposed.sql.SqlExpressionBuilder -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.transactions.TransactionManager -import org.jetbrains.exposed.sql.transactions.transaction +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext fun Route.articles() { @@ -76,6 +79,23 @@ fun Route.articles() { createWithResource(ArticleService::add, authPredicate = { user, article -> article.createdBy eqr user }) } + get("/search") { + val query = call.parameters["q"] + println("q: $query") + val json = jacksonObjectMapper() + + val client = HttpClient() + + val res = client.get("http://172.18.0.2:9200/articles/_search?q=$query") + + val test = withContext(Dispatchers.IO) { json.readValue>(res) } + println(res) + println() + val src = test.hits.hits.map { l -> l._source } + src.forEach { l -> println(l) } + call.respond(src) + } + articleComments() } From 71ca2f8b1ec8c4c0e5bd0a8cb59472f4008af337 Mon Sep 17 00:00:00 2001 From: Hamza Date: Fri, 1 Nov 2019 18:14:16 +0500 Subject: [PATCH 02/75] Article is added to index on create --- build.gradle.kts | 7 ++++-- .../backend/routes/articles/ArticleRoutes.kt | 23 +++++++++++++++++-- .../backend/routes/handling/Handlers.kt | 6 +++-- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 01fa241b..4fa5e6f0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -46,6 +46,11 @@ dependencies { compile("io.ktor:ktor-auth-jwt:$ktor_version") compile("io.ktor:ktor-jackson:$ktor_version") + // Ktor client + + compile("io.ktor:ktor-client-cio:$ktor_version") + + // Database stuff compile("org.postgresql:postgresql:$pg_driver_version") @@ -69,8 +74,6 @@ dependencies { compile("com.github.kittinunf.result:result:2.2.0") compile("com.github.kittinunf.result:result-coroutines:2.2.0") - compile("io.ktor:ktor-client-cio:$ktor_version") - // JJWT compile("io.jsonwebtoken:jjwt-api:0.10.7") diff --git a/src/blogify/backend/routes/articles/ArticleRoutes.kt b/src/blogify/backend/routes/articles/ArticleRoutes.kt index 64093195..0963e460 100644 --- a/src/blogify/backend/routes/articles/ArticleRoutes.kt +++ b/src/blogify/backend/routes/articles/ArticleRoutes.kt @@ -20,9 +20,14 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import io.ktor.client.HttpClient import io.ktor.client.request.get +import io.ktor.client.request.post +import io.ktor.client.request.url +import io.ktor.content.TextContent +import io.ktor.http.ContentType import io.ktor.response.respond import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import java.util.* fun Route.articles() { @@ -76,7 +81,21 @@ fun Route.articles() { } post("/") { - createWithResource(ArticleService::add, authPredicate = { user, article -> article.createdBy eqr user }) + createWithResource( + ArticleService::add, + authPredicate = { user, article -> article.createdBy eqr user }, + doAfter = { + val client = HttpClient() + val json = jacksonObjectMapper() + val strjSON = json.writeValueAsString(it) + println(strjSON) + println() + val res = client.post { + url("http://es:9200/articles/_create/${it.uuid}") + body = TextContent(strjSON, contentType = ContentType.Application.Json) + } + println(res) + }) } get("/search") { @@ -86,7 +105,7 @@ fun Route.articles() { val client = HttpClient() - val res = client.get("http://172.18.0.2:9200/articles/_search?q=$query") + val res = client.get("http://es:9200/articles/_search?q=$query") val test = withContext(Dispatchers.IO) { json.readValue>(res) } println(res) diff --git a/src/blogify/backend/routes/handling/Handlers.kt b/src/blogify/backend/routes/handling/Handlers.kt index a38ee301..b3e0e274 100644 --- a/src/blogify/backend/routes/handling/Handlers.kt +++ b/src/blogify/backend/routes/handling/Handlers.kt @@ -419,7 +419,8 @@ suspend inline fun CallPipeline.deleteOnResource ( @BlogifyDsl suspend inline fun CallPipeline.createWithResource ( noinline create: suspend (R) -> ResourceResult, - noinline authPredicate: suspend (User, R) -> Boolean = defaultPredicateLambda + noinline authPredicate: suspend (User, R) -> Boolean = defaultPredicateLambda, + noinline doAfter: suspend (R) -> Unit = {} ) { try { @@ -431,6 +432,7 @@ suspend inline fun CallPipeline.createWithResource ( res.fold ( success = { call.respond(HttpStatusCode.Created) + doAfter(rec) }, failure = call::respondExceptionMessage ) @@ -446,7 +448,7 @@ suspend inline fun CallPipeline.createWithResource ( } catch (e: ContentTransformationException) { call.respond(HttpStatusCode.BadRequest) } -} // KT-33440 | Doesn't compile when lambda called with invoke() for now */ +} // KT-33440 | Doesn't compile when lambda called with invoke() for now /** * Adds a handler to a [CallPipeline] that handles deleting a new resource. From f1cc6f9cae275d3c4f359fcbddf2adc2e99318fe Mon Sep 17 00:00:00 2001 From: Hamza Date: Fri, 1 Nov 2019 18:29:09 +0500 Subject: [PATCH 03/75] search: Made the code much better --- .../backend/routes/articles/ArticleRoutes.kt | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/src/blogify/backend/routes/articles/ArticleRoutes.kt b/src/blogify/backend/routes/articles/ArticleRoutes.kt index 0963e460..62f6cf96 100644 --- a/src/blogify/backend/routes/articles/ArticleRoutes.kt +++ b/src/blogify/backend/routes/articles/ArticleRoutes.kt @@ -25,9 +25,6 @@ import io.ktor.client.request.url import io.ktor.content.TextContent import io.ktor.http.ContentType import io.ktor.response.respond -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import java.util.* fun Route.articles() { @@ -84,35 +81,33 @@ fun Route.articles() { createWithResource( ArticleService::add, authPredicate = { user, article -> article.createdBy eqr user }, - doAfter = { - val client = HttpClient() - val json = jacksonObjectMapper() - val strjSON = json.writeValueAsString(it) - println(strjSON) - println() - val res = client.post { - url("http://es:9200/articles/_create/${it.uuid}") - body = TextContent(strjSON, contentType = ContentType.Application.Json) + doAfter = { article -> + HttpClient().use { client -> + val objectMapper = jacksonObjectMapper() + val jsonAsString = objectMapper.writeValueAsString(article) + println(jsonAsString) + client.post { + url("http://es:9200/articles/_create/${article.uuid}") + body = TextContent(jsonAsString, contentType = ContentType.Application.Json) + }.also { println(it) } } - println(res) - }) + } + ) } get("/search") { - val query = call.parameters["q"] - println("q: $query") - val json = jacksonObjectMapper() - - val client = HttpClient() - - val res = client.get("http://es:9200/articles/_search?q=$query") - - val test = withContext(Dispatchers.IO) { json.readValue>(res) } - println(res) - println() - val src = test.hits.hits.map { l -> l._source } - src.forEach { l -> println(l) } - call.respond(src) + call.parameters["q"]?.let { query -> + HttpClient().use { client -> + val objectMapper = jacksonObjectMapper() + + val request = client.get("http://es:9200/articles/_search?q=$query").also { println(it) } + val parsedResponse = objectMapper.readValue>(request) + val hits = parsedResponse.hits.hits.map { l -> l._source } + println("hits") + hits.forEach { println(it) } + call.respond(hits) + } + } } articleComments() From 141fb5f8fe62d8c18482cd428bba6e5307de9f2c Mon Sep 17 00:00:00 2001 From: Hamza Date: Fri, 1 Nov 2019 19:21:15 +0500 Subject: [PATCH 04/75] Using volume for elastic search --- docker-compose.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index b675b0f0..2aea6e6f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,7 +24,10 @@ services: environment: - 'discovery.type=single-node' - 'node.name=es' + volumes: + - 'es-data:/usr/share/elasticsearch/data' volumes: db-data: static-data: + es-data: From 18463921314ec191561b0b55281bac0fcb14a95d Mon Sep 17 00:00:00 2001 From: Hamza Date: Fri, 1 Nov 2019 20:00:35 +0500 Subject: [PATCH 05/75] search: you can request for specific properties --- .../backend/routes/articles/ArticleRoutes.kt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/blogify/backend/routes/articles/ArticleRoutes.kt b/src/blogify/backend/routes/articles/ArticleRoutes.kt index 62f6cf96..270d00f9 100644 --- a/src/blogify/backend/routes/articles/ArticleRoutes.kt +++ b/src/blogify/backend/routes/articles/ArticleRoutes.kt @@ -96,13 +96,24 @@ fun Route.articles() { } get("/search") { - call.parameters["q"]?.let { query -> + val params = call.parameters + val selectedPropertyNames = params["fields"]?.split(",")?.toSet() + params["q"]?.let { query -> HttpClient().use { client -> val objectMapper = jacksonObjectMapper() val request = client.get("http://es:9200/articles/_search?q=$query").also { println(it) } val parsedResponse = objectMapper.readValue>(request) val hits = parsedResponse.hits.hits.map { l -> l._source } + try { + selectedPropertyNames?.let { props -> + + call.respond(hits.map { it.slice(props) }) + + } ?: call.respond(hits.map { it.sanitize() }) + } catch (bruhMoment: Service.Exception) { + call.respondExceptionMessage(bruhMoment) + } println("hits") hits.forEach { println(it) } call.respond(hits) From 6dd5f20b18f8b417e42457e485dd90a13cfff5b7 Mon Sep 17 00:00:00 2001 From: Hamza Date: Fri, 1 Nov 2019 20:22:53 +0500 Subject: [PATCH 06/75] search: (probably bad) frontend implementation --- .../frontend/src/app/app-routing.module.ts | 2 + .../app/services/article/article.service.ts | 6 +++ .../components/search/search.component.html | 4 ++ .../components/search/search.component.scss | 0 .../search/search.component.spec.ts | 25 ++++++++++++ .../components/search/search.component.ts | 39 +++++++++++++++++++ .../show-all-articles.component.html | 2 +- .../frontend/src/app/shared/shared.module.ts | 8 +++- src/blogify/frontend/src/proxy.conf.json | 2 +- 9 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 src/blogify/frontend/src/app/shared/components/search/search.component.html create mode 100644 src/blogify/frontend/src/app/shared/components/search/search.component.scss create mode 100644 src/blogify/frontend/src/app/shared/components/search/search.component.spec.ts create mode 100644 src/blogify/frontend/src/app/shared/components/search/search.component.ts diff --git a/src/blogify/frontend/src/app/app-routing.module.ts b/src/blogify/frontend/src/app/app-routing.module.ts index baaec9ef..4de20c59 100644 --- a/src/blogify/frontend/src/app/app-routing.module.ts +++ b/src/blogify/frontend/src/app/app-routing.module.ts @@ -6,6 +6,7 @@ import { ProfileComponent } from './components/profile/profile.component'; import { NewArticleComponent } from './components/newarticle/new-article.component'; import { ShowArticleComponent } from './components/show-article/show-article.component'; import { UpdateArticleComponent } from './components/update-article/update-article.component'; +import { SearchComponent } from "./shared/components/search/search.component"; const routes: Routes = [ @@ -17,6 +18,7 @@ const routes: Routes = [ { path: 'profile/**', component: ProfileComponent }, { path: 'article/:uuid', component: ShowArticleComponent }, { path: 'article/update/:uuid', component: UpdateArticleComponent }, + { path: 'search', component: SearchComponent }, ]; @NgModule({ diff --git a/src/blogify/frontend/src/app/services/article/article.service.ts b/src/blogify/frontend/src/app/services/article/article.service.ts index 2baf4976..65fa21b2 100644 --- a/src/blogify/frontend/src/app/services/article/article.service.ts +++ b/src/blogify/frontend/src/app/services/article/article.service.ts @@ -3,6 +3,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Article } from '../../models/Article'; import { AuthService } from '../../shared/auth/auth.service'; import * as uuid from 'uuid/v4'; +import {consoleTestResultHandler} from "tslint/lib/test"; @Injectable({ providedIn: 'root' @@ -101,4 +102,9 @@ export class ArticleService { return this.httpClient.delete(`/api/articles/${uuid}`, httpOptions).toPromise(); } + search(query: string, fields: string[]) { + const url = `/api/articles/search/?q=${query}&fields=${fields.join(',')}` + console.log(url) + return this.httpClient.get(url).toPromise(); + } } diff --git a/src/blogify/frontend/src/app/shared/components/search/search.component.html b/src/blogify/frontend/src/app/shared/components/search/search.component.html new file mode 100644 index 00000000..c68be83b --- /dev/null +++ b/src/blogify/frontend/src/app/shared/components/search/search.component.html @@ -0,0 +1,4 @@ +

search works!

+ + +

{{articles | json}}

diff --git a/src/blogify/frontend/src/app/shared/components/search/search.component.scss b/src/blogify/frontend/src/app/shared/components/search/search.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/blogify/frontend/src/app/shared/components/search/search.component.spec.ts b/src/blogify/frontend/src/app/shared/components/search/search.component.spec.ts new file mode 100644 index 00000000..43729199 --- /dev/null +++ b/src/blogify/frontend/src/app/shared/components/search/search.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SearchComponent } from './search.component'; + +describe('SearchComponent', () => { + let component: SearchComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SearchComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SearchComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/blogify/frontend/src/app/shared/components/search/search.component.ts b/src/blogify/frontend/src/app/shared/components/search/search.component.ts new file mode 100644 index 00000000..0c886865 --- /dev/null +++ b/src/blogify/frontend/src/app/shared/components/search/search.component.ts @@ -0,0 +1,39 @@ +import {Component, OnInit} from '@angular/core'; +import {Article} from "../../../models/Article"; +import {ArticleService} from "../../../services/article/article.service"; + +@Component({ + selector: 'app-search', + templateUrl: './search.component.html', + styleUrls: ['./search.component.scss'] +}) +export class SearchComponent implements OnInit { + query = ''; + articles: Article[] = []; + + constructor(private articleService: ArticleService) { + } + + ngOnInit() { + console.log("search component") + } + + async search() { + if ((Math.random() * 4) > 3) { + this.articles = await this.articleService.search( + this.query, + ['title', 'summary'] + ) + } + } + + forceSearch() { + this.articleService.search( + this.query, + ['title', 'summary'] + ).then(it => { + console.log(it) + this.articles = it + }) + } +} diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html index 8e7ab378..05797e1c 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html @@ -9,7 +9,7 @@

{{title}}

- + diff --git a/src/blogify/frontend/src/app/shared/shared.module.ts b/src/blogify/frontend/src/app/shared/shared.module.ts index 66a68502..4c67b4a8 100644 --- a/src/blogify/frontend/src/app/shared/shared.module.ts +++ b/src/blogify/frontend/src/app/shared/shared.module.ts @@ -9,6 +9,8 @@ import { RelativeTimePipe } from './relative-time/relative-time.pipe'; import { UserDisplayComponent } from './components/user-display/user-display.component'; import { DarkThemeDirective } from './directives/dark-theme/dark-theme.directive'; import { CompactDirective } from './directives/compact/compact.directive'; +import { SearchComponent } from "./components/search/search.component"; +import {FormsModule} from "@angular/forms"; @NgModule({ declarations: [ @@ -18,12 +20,14 @@ import { CompactDirective } from './directives/compact/compact.directive'; TabHeaderComponent, ProfilePictureComponent, ShowAllArticlesComponent, - UserDisplayComponent + UserDisplayComponent, + SearchComponent, ], imports: [ CommonModule, ProfileRoutingModule, - FontAwesomeModule + FontAwesomeModule, + FormsModule, ], exports: [ RelativeTimePipe, diff --git a/src/blogify/frontend/src/proxy.conf.json b/src/blogify/frontend/src/proxy.conf.json index 4772242b..f4e74dd5 100644 --- a/src/blogify/frontend/src/proxy.conf.json +++ b/src/blogify/frontend/src/proxy.conf.json @@ -1,6 +1,6 @@ { "/api": { - "target": "http://172.105.28.10:8080/", + "target": "http://localhost:8080/", "secure": false } } From 6cdc6c3f913b25aee60d1fd1cda6e13da35b4c00 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Sun, 3 Nov 2019 00:52:13 -0400 Subject: [PATCH 07/75] search: kdoc stubs for new classes, formatting --- .../backend/resources/search/Search.kt | 54 ++++++++++++------- .../backend/routes/pipelines/Pipelines.kt | 1 + 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/blogify/backend/resources/search/Search.kt b/src/blogify/backend/resources/search/Search.kt index 645da02a..e40f1af6 100644 --- a/src/blogify/backend/resources/search/Search.kt +++ b/src/blogify/backend/resources/search/Search.kt @@ -2,36 +2,54 @@ package blogify.backend.resources.search import blogify.backend.resources.models.Resource -data class Search( - val _shards: Shards, - val hits: Hits, +/** + * TODO complete doc + * + * @author hamza1311 + */ +data class Search ( + val _shards: Shards, + val hits: Hits, val timed_out: Boolean, - val took: Int + val took: Int ) { - data class Shards( - val failed: Int, - val skipped: Int, + + /** + * TODO complete doc + */ + data class Shards ( + val failed: Int, + val skipped: Int, val successful: Int, - val total: Int + val total: Int ) - data class Hits( - val hits: List>, + /** + * TODO complete doc + */ + data class Hits ( + val hits: List>, val max_score: Double, - val total: Total + val total: Total ) - data class Hit( - val _id: String, - val _index: String, - val _score: Double, + /** + * TODO complete doc + */ + data class Hit ( + val _id: String, + val _index: String, + val _score: Double, val _source: S, - val _type: String + val _type: String ) - data class Total( + /** + * TODO complete doc + */ + data class Total ( val relation: String, - val value: Int + val value: Int ) } \ No newline at end of file diff --git a/src/blogify/backend/routes/pipelines/Pipelines.kt b/src/blogify/backend/routes/pipelines/Pipelines.kt index d2603d6d..8fab140b 100644 --- a/src/blogify/backend/routes/pipelines/Pipelines.kt +++ b/src/blogify/backend/routes/pipelines/Pipelines.kt @@ -14,6 +14,7 @@ import io.ktor.util.pipeline.PipelineContext import io.ktor.util.pipeline.PipelineInterceptor import com.andreapivetta.kolor.red + import org.slf4j.LoggerFactory private val logger = LoggerFactory.getLogger("blogify-pipeline-manager") From 53263971c1fec5bf2f8c249ad40ab33e2c238c0a Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Sun, 3 Nov 2019 10:23:45 -0500 Subject: [PATCH 08/75] stuff --- build.gradle.kts | 3 ++- .../backend/resources/search/Search.kt | 2 +- .../backend/routes/articles/ArticleRoutes.kt | 20 ++++++++++--------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 4fa5e6f0..5cb7a880 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -49,7 +49,8 @@ dependencies { // Ktor client compile("io.ktor:ktor-client-cio:$ktor_version") - + compile("io.ktor:ktor-client-json:$ktor_version") + compile("io.ktor:ktor-client-json-jvm:$ktor_version") // Database stuff diff --git a/src/blogify/backend/resources/search/Search.kt b/src/blogify/backend/resources/search/Search.kt index e40f1af6..112139f2 100644 --- a/src/blogify/backend/resources/search/Search.kt +++ b/src/blogify/backend/resources/search/Search.kt @@ -52,4 +52,4 @@ data class Search ( val value: Int ) -} \ No newline at end of file +} diff --git a/src/blogify/backend/routes/articles/ArticleRoutes.kt b/src/blogify/backend/routes/articles/ArticleRoutes.kt index 270d00f9..632d01ea 100644 --- a/src/blogify/backend/routes/articles/ArticleRoutes.kt +++ b/src/blogify/backend/routes/articles/ArticleRoutes.kt @@ -2,9 +2,6 @@ package blogify.backend.routes.articles -import io.ktor.application.call -import io.ktor.routing.* - import blogify.backend.database.Articles import blogify.backend.database.Users import blogify.backend.resources.Article @@ -16,16 +13,21 @@ import blogify.backend.routes.handling.* import blogify.backend.services.UserService import blogify.backend.services.articles.ArticleService import blogify.backend.services.models.Service -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue + +import io.ktor.application.call +import io.ktor.routing.* import io.ktor.client.HttpClient import io.ktor.client.request.get import io.ktor.client.request.post import io.ktor.client.request.url +import io.ktor.client.features.json.JsonFeature import io.ktor.content.TextContent import io.ktor.http.ContentType import io.ktor.response.respond +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue + fun Route.articles() { route("/articles") { @@ -99,12 +101,12 @@ fun Route.articles() { val params = call.parameters val selectedPropertyNames = params["fields"]?.split(",")?.toSet() params["q"]?.let { query -> - HttpClient().use { client -> + HttpClient() { install(JsonFeature) }.use { client -> val objectMapper = jacksonObjectMapper() - val request = client.get("http://es:9200/articles/_search?q=$query").also { println(it) } - val parsedResponse = objectMapper.readValue>(request) - val hits = parsedResponse.hits.hits.map { l -> l._source } + val request = client.get("http://es:9200/articles/_search?q=$query") + val parsed = objectMapper.readValue>(request) + val hits = parsed.hits.hits.map { l -> l._source } try { selectedPropertyNames?.let { props -> From cf91c79c301199c7e7dd64a71cd8e11d3d1f46d4 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Sun, 3 Nov 2019 14:08:13 -0500 Subject: [PATCH 09/75] search: stub in SAA --- .../show-all-articles/show-all-articles.component.html | 2 +- .../show-all-articles/show-all-articles.component.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html index 05797e1c..e01ec793 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html @@ -6,7 +6,7 @@

{{title}}

- + diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts index 21cda63c..a06eb075 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts @@ -28,6 +28,10 @@ export class ShowAllArticlesComponent implements OnInit { ngOnInit() {} + async startSearch() { + alert('searching !') + } + async navigateToNewArticle() { if (this.authService.userToken === '') { const url = `/login?redirect=/new-article`; From 1e9e9d23d97bb27dcc5fed781367224376fd89c5 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Sun, 3 Nov 2019 14:12:23 -0500 Subject: [PATCH 10/75] search: use jackson for serializing --- build.gradle.kts | 1 + src/blogify/backend/routes/articles/ArticleRoutes.kt | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5cb7a880..566b20a8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -51,6 +51,7 @@ dependencies { compile("io.ktor:ktor-client-cio:$ktor_version") compile("io.ktor:ktor-client-json:$ktor_version") compile("io.ktor:ktor-client-json-jvm:$ktor_version") + compile("io.ktor:ktor-client-jackson:$ktor_version") // Database stuff diff --git a/src/blogify/backend/routes/articles/ArticleRoutes.kt b/src/blogify/backend/routes/articles/ArticleRoutes.kt index 632d01ea..1f77243b 100644 --- a/src/blogify/backend/routes/articles/ArticleRoutes.kt +++ b/src/blogify/backend/routes/articles/ArticleRoutes.kt @@ -102,10 +102,7 @@ fun Route.articles() { val selectedPropertyNames = params["fields"]?.split(",")?.toSet() params["q"]?.let { query -> HttpClient() { install(JsonFeature) }.use { client -> - val objectMapper = jacksonObjectMapper() - - val request = client.get("http://es:9200/articles/_search?q=$query") - val parsed = objectMapper.readValue>(request) + val parsed = client.get>("http://es:9200/articles/_search?q=$query") val hits = parsed.hits.hits.map { l -> l._source } try { selectedPropertyNames?.let { props -> From 552d2a8f5a70fba77de569fe4a06f8045c46149e Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Sun, 3 Nov 2019 21:10:23 -0500 Subject: [PATCH 11/75] search: moved search results UI to SAA --- .../frontend/src/app/app-routing.module.ts | 4 +- .../app/services/article/article.service.ts | 5 +- .../components/search/search.component.html | 4 - .../components/search/search.component.scss | 0 .../search/search.component.spec.ts | 25 ---- .../components/search/search.component.ts | 39 ------ .../show-all-articles.component.html | 54 ++------- .../show-all-articles.component.scss | 114 +----------------- .../show-all-articles.component.ts | 22 +++- .../frontend/src/app/shared/shared.module.ts | 4 +- 10 files changed, 41 insertions(+), 230 deletions(-) delete mode 100644 src/blogify/frontend/src/app/shared/components/search/search.component.html delete mode 100644 src/blogify/frontend/src/app/shared/components/search/search.component.scss delete mode 100644 src/blogify/frontend/src/app/shared/components/search/search.component.spec.ts delete mode 100644 src/blogify/frontend/src/app/shared/components/search/search.component.ts diff --git a/src/blogify/frontend/src/app/app-routing.module.ts b/src/blogify/frontend/src/app/app-routing.module.ts index 4de20c59..e0a502a7 100644 --- a/src/blogify/frontend/src/app/app-routing.module.ts +++ b/src/blogify/frontend/src/app/app-routing.module.ts @@ -5,8 +5,7 @@ import { LoginComponent } from './components/login/login.component'; import { ProfileComponent } from './components/profile/profile.component'; import { NewArticleComponent } from './components/newarticle/new-article.component'; import { ShowArticleComponent } from './components/show-article/show-article.component'; -import { UpdateArticleComponent } from './components/update-article/update-article.component'; -import { SearchComponent } from "./shared/components/search/search.component"; +import { UpdateArticleComponent } from './components/update-article/update-article.component';; const routes: Routes = [ @@ -18,7 +17,6 @@ const routes: Routes = [ { path: 'profile/**', component: ProfileComponent }, { path: 'article/:uuid', component: ShowArticleComponent }, { path: 'article/update/:uuid', component: UpdateArticleComponent }, - { path: 'search', component: SearchComponent }, ]; @NgModule({ diff --git a/src/blogify/frontend/src/app/services/article/article.service.ts b/src/blogify/frontend/src/app/services/article/article.service.ts index 65fa21b2..05703953 100644 --- a/src/blogify/frontend/src/app/services/article/article.service.ts +++ b/src/blogify/frontend/src/app/services/article/article.service.ts @@ -103,8 +103,7 @@ export class ArticleService { } search(query: string, fields: string[]) { - const url = `/api/articles/search/?q=${query}&fields=${fields.join(',')}` - console.log(url) - return this.httpClient.get(url).toPromise(); + const url = `/api/articles/search/?q=${query}&fields=${fields.join(',')}`; + return this.httpClient.get(url).toPromise(); } } diff --git a/src/blogify/frontend/src/app/shared/components/search/search.component.html b/src/blogify/frontend/src/app/shared/components/search/search.component.html deleted file mode 100644 index c68be83b..00000000 --- a/src/blogify/frontend/src/app/shared/components/search/search.component.html +++ /dev/null @@ -1,4 +0,0 @@ -

search works!

- - -

{{articles | json}}

diff --git a/src/blogify/frontend/src/app/shared/components/search/search.component.scss b/src/blogify/frontend/src/app/shared/components/search/search.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/src/blogify/frontend/src/app/shared/components/search/search.component.spec.ts b/src/blogify/frontend/src/app/shared/components/search/search.component.spec.ts deleted file mode 100644 index 43729199..00000000 --- a/src/blogify/frontend/src/app/shared/components/search/search.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { SearchComponent } from './search.component'; - -describe('SearchComponent', () => { - let component: SearchComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ SearchComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(SearchComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/blogify/frontend/src/app/shared/components/search/search.component.ts b/src/blogify/frontend/src/app/shared/components/search/search.component.ts deleted file mode 100644 index 0c886865..00000000 --- a/src/blogify/frontend/src/app/shared/components/search/search.component.ts +++ /dev/null @@ -1,39 +0,0 @@ -import {Component, OnInit} from '@angular/core'; -import {Article} from "../../../models/Article"; -import {ArticleService} from "../../../services/article/article.service"; - -@Component({ - selector: 'app-search', - templateUrl: './search.component.html', - styleUrls: ['./search.component.scss'] -}) -export class SearchComponent implements OnInit { - query = ''; - articles: Article[] = []; - - constructor(private articleService: ArticleService) { - } - - ngOnInit() { - console.log("search component") - } - - async search() { - if ((Math.random() * 4) > 3) { - this.articles = await this.articleService.search( - this.query, - ['title', 'summary'] - ) - } - } - - forceSearch() { - this.articleService.search( - this.query, - ['title', 'summary'] - ).then(it => { - console.log(it) - this.articles = it - }) - } -} diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html index e01ec793..53413c64 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html @@ -1,12 +1,17 @@
+ + + + + -

{{title}}

+

{{showingSearchResults ? 'Search results' : title}}

- + @@ -19,45 +24,12 @@

{{title}}

-
- -
- -

{{article.title}}

- - - -
- -
- - {{article.summary}} - - - {{article.createdAt | relativeTime}} -
- -

- Read More -

- -
- - -

4

- - - - - - - No tags - -
+
+ +
-
+
+ +
diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss index b1a9ac7d..40f692b6 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss @@ -16,6 +16,10 @@ $search-icon-break: 1300px; + #header-search-back { + margin-right: 1.15em; + } + #header-title { margin-right: 1em; } @@ -42,114 +46,4 @@ } } - .article { - - display: flex; - flex-direction: column; - justify-content: center; - align-items: flex-start; - - color: var(--card-fg); - background: var(--card-bg); - - border-radius: $std-border-radius; - border: none; - - $box-shadow: 0 0 6px 1px rgba(0, 0, 0, 0.20); - -webkit-box-shadow: $box-shadow; - -moz-box-shadow: $box-shadow; - box-shadow: $box-shadow;; - - padding: 1.2em 1.5em; - - margin-top: 1.25em; - - .article-first-line { - width: 100%; - - text-align: center; - - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - - .header-title { - text-align: left; - } - - .article-author { - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - - .author-pfp { - margin-right: .85em; - } - - .author-name { font-size: 1.7em; font-weight: 600; } - - } - - } - - .article-second-line { - width: 100%; - - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - - .article-summary { font-size: 1.25em; } - .article-posted-at { font-size: 1.30em; } - } - - .article-read-more { color: var(--card-fg); } - - .article-last-line { - width: 100%; - - text-align: center; - - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - } - - .article-comments-count { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - - fa-icon:first-child { margin-right: .75em; } - - align-self: flex-end; - } - - .article-no-tags, - .article-tags { - display: flex; - align-self: flex-end; - - margin-top: 1.2em; - - & > * { - font-size: 1.35em; - margin: 0 .25em; - padding: .15em .65em; - - border-radius: .3em; - - &:not(:nth-child(1)) { background-color: var(--card-ct); } - &:nth-child(1) { margin-right: 0; padding-right: 0; } - &:last-child { margin-right: 0 } - } - } - - } - } diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts index a06eb075..2ad9b992 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts @@ -3,7 +3,8 @@ import { Article } from '../../../models/Article'; import { AuthService } from '../../auth/auth.service'; import { Router } from '@angular/router'; import { StaticContentService } from '../../../services/static/static-content.service'; -import { faCommentAlt, faPencilAlt, faSearch } from '@fortawesome/free-solid-svg-icons'; +import { faArrowLeft, faPencilAlt, faSearch } from '@fortawesome/free-solid-svg-icons'; +import {ArticleService} from '../../../services/article/article.service'; @Component({ selector: 'app-show-all-articles', @@ -14,14 +15,19 @@ export class ShowAllArticlesComponent implements OnInit { faSearch = faSearch; faPencil = faPencilAlt; - faCommentAlt = faCommentAlt; + faArrowLeft = faArrowLeft; @Input() title = 'Articles'; @Input() articles: Article[]; @Input() allowCreate = true; + showingSearchResults = false; + searchQuery: string; + searchResults: Article[]; + constructor ( private authService: AuthService, + private articleService: ArticleService, private staticContentService: StaticContentService, private router: Router ) {} @@ -29,7 +35,17 @@ export class ShowAllArticlesComponent implements OnInit { ngOnInit() {} async startSearch() { - alert('searching !') + this.articleService.search ( + this.searchQuery, + ['title', 'summary', 'createdBy', 'categories', 'createdAt'] + ).then(it => { + this.searchResults = it; + this.showingSearchResults = true; + }) + } + + async stopSearch() { + this.showingSearchResults = false; } async navigateToNewArticle() { diff --git a/src/blogify/frontend/src/app/shared/shared.module.ts b/src/blogify/frontend/src/app/shared/shared.module.ts index 4c67b4a8..057f2778 100644 --- a/src/blogify/frontend/src/app/shared/shared.module.ts +++ b/src/blogify/frontend/src/app/shared/shared.module.ts @@ -9,8 +9,8 @@ import { RelativeTimePipe } from './relative-time/relative-time.pipe'; import { UserDisplayComponent } from './components/user-display/user-display.component'; import { DarkThemeDirective } from './directives/dark-theme/dark-theme.directive'; import { CompactDirective } from './directives/compact/compact.directive'; -import { SearchComponent } from "./components/search/search.component"; import {FormsModule} from "@angular/forms"; +import { SingleArticleBoxComponent } from './components/show-all-articles/single-article-box/single-article-box.component'; @NgModule({ declarations: [ @@ -21,7 +21,7 @@ import {FormsModule} from "@angular/forms"; ProfilePictureComponent, ShowAllArticlesComponent, UserDisplayComponent, - SearchComponent, + SingleArticleBoxComponent, ], imports: [ CommonModule, From 8557d77f4a8241893a0bd35b4c25085532030fc6 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Sun, 3 Nov 2019 21:55:39 -0500 Subject: [PATCH 12/75] markdown: improved code highlight, added support for python and kotlin --- src/blogify/frontend/angular.json | 4 +++- src/blogify/frontend/package.json | 1 + src/blogify/frontend/src/styles.scss | 2 +- src/blogify/frontend/src/styles/code.scss | 11 +++++++++++ src/blogify/frontend/src/styles/fonts.scss | 3 ++- 5 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 src/blogify/frontend/src/styles/code.scss diff --git a/src/blogify/frontend/angular.json b/src/blogify/frontend/angular.json index e26a196a..04b9fc30 100644 --- a/src/blogify/frontend/angular.json +++ b/src/blogify/frontend/angular.json @@ -30,7 +30,7 @@ "styles": [ "./node_modules/font-awesome/scss/font-awesome.scss", "src/styles.scss", - "node_modules/prismjs/themes/prism-okaidia.css", + "node_modules/prismjs/themes/prism-twilight.css", "node_modules/prismjs/plugins/line-numbers/prism-line-numbers.css", "node_modules/prismjs/plugins/line-highlight/prism-line-highlight.css" ], @@ -43,6 +43,8 @@ "node_modules/marked/lib/marked.js", "node_modules/prismjs/prism.js", "node_modules/prismjs/components/prism-csharp.min.js", + "node_modules/prismjs/components/prism-python.min.js", + "node_modules/prismjs/components/prism-kotlin.min.js", "node_modules/prismjs/components/prism-css.min.js", "node_modules/prismjs/plugins/line-numbers/prism-line-numbers.js", "node_modules/prismjs/plugins/line-highlight/prism-line-highlight.js" diff --git a/src/blogify/frontend/package.json b/src/blogify/frontend/package.json index f54b8ec8..4df4092a 100644 --- a/src/blogify/frontend/package.json +++ b/src/blogify/frontend/package.json @@ -25,6 +25,7 @@ "angular-font-awesome": "^3.1.2", "font-awesome": "^4.7.0", "ngx-markdown": "^8.2.1", + "prismjs": "^1.17.1", "rxjs": "~6.4.0", "tslib": "^1.10.0", "uuid": "^3.3.3", diff --git a/src/blogify/frontend/src/styles.scss b/src/blogify/frontend/src/styles.scss index d067209a..7eec82d6 100644 --- a/src/blogify/frontend/src/styles.scss +++ b/src/blogify/frontend/src/styles.scss @@ -6,6 +6,7 @@ @import "styles/layouts"; @import "styles/forms"; @import "styles/titles"; +@import "styles/code"; * { transition: color 150ms ease-in-out, background-color 150ms ease-in-out; @@ -17,7 +18,6 @@ text-decoration: none; border: none; outline: none; - font-family: $nunito; font-size: 10px; } diff --git a/src/blogify/frontend/src/styles/code.scss b/src/blogify/frontend/src/styles/code.scss new file mode 100644 index 00000000..12a2a85b --- /dev/null +++ b/src/blogify/frontend/src/styles/code.scss @@ -0,0 +1,11 @@ +@import "fonts"; + +code * { font-family: $source; } + +pre[class*="language-"] { + border: none !important; + box-shadow: none !important; + * { text-shadow: none !important; } + + background: var(--card-bg) !important; +} diff --git a/src/blogify/frontend/src/styles/fonts.scss b/src/blogify/frontend/src/styles/fonts.scss index 244a54ea..7d79295a 100644 --- a/src/blogify/frontend/src/styles/fonts.scss +++ b/src/blogify/frontend/src/styles/fonts.scss @@ -1,7 +1,8 @@ @import url('https://fonts.googleapis.com/css?family=Nunito:400,700&display=swap'); +@import url('https://fonts.googleapis.com/css?family=Source+Code+Pro&display=swap'); -$lexenda: 'Lexend Deca', sans-serif; $nunito: 'Nunito', sans-serif; +$source: 'Source Code Pro', sans-serif; a:hover, a:hover * { color: var(--accent-neutral) !important; From 24806762605948aa4a71e078932169570877fbc6 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Sun, 3 Nov 2019 22:09:44 -0500 Subject: [PATCH 13/75] comments: use user-display in comment --- .../comment/single-comment/single-comment.component.html | 2 +- .../comment/single-comment/single-comment.component.scss | 5 +++-- .../shared/components/user-display/user-display.component.ts | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/blogify/frontend/src/app/components/comment/single-comment/single-comment.component.html b/src/blogify/frontend/src/app/components/comment/single-comment/single-comment.component.html index ac290f99..9335cc78 100644 --- a/src/blogify/frontend/src/app/components/comment/single-comment/single-comment.component.html +++ b/src/blogify/frontend/src/app/components/comment/single-comment/single-comment.component.html @@ -1,7 +1,7 @@
- {{usernameText()}} +
diff --git a/src/blogify/frontend/src/app/components/comment/single-comment/single-comment.component.scss b/src/blogify/frontend/src/app/components/comment/single-comment/single-comment.component.scss index 66efbdbf..90e8d202 100644 --- a/src/blogify/frontend/src/app/components/comment/single-comment/single-comment.component.scss +++ b/src/blogify/frontend/src/app/components/comment/single-comment/single-comment.component.scss @@ -17,11 +17,12 @@ } .comment-content { - + margin-top: .5em; + * { font-size: 1.15em; } } .comment-buttons { - + margin-top: .5em; } } diff --git a/src/blogify/frontend/src/app/shared/components/user-display/user-display.component.ts b/src/blogify/frontend/src/app/shared/components/user-display/user-display.component.ts index f1099df1..5ed4e548 100644 --- a/src/blogify/frontend/src/app/shared/components/user-display/user-display.component.ts +++ b/src/blogify/frontend/src/app/shared/components/user-display/user-display.component.ts @@ -8,7 +8,7 @@ import { User } from '../../../models/User'; }) export class UserDisplayComponent implements OnInit { - readonly EM_SIZE_TEXT_RATIO = 2.6; + readonly EM_SIZE_TEXT_RATIO = 2.4; @Input() user: User; @Input() info: 'username' | 'name' = 'username'; From 3edcf92c83addc8a6132d2a2d2dd0f43b3067427 Mon Sep 17 00:00:00 2001 From: Hamza Date: Mon, 4 Nov 2019 18:33:15 +0500 Subject: [PATCH 14/75] Fix for conflict (might not work) --- src/blogify/backend/routes/handling/Handlers.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blogify/backend/routes/handling/Handlers.kt b/src/blogify/backend/routes/handling/Handlers.kt index b3e0e274..32deab82 100644 --- a/src/blogify/backend/routes/handling/Handlers.kt +++ b/src/blogify/backend/routes/handling/Handlers.kt @@ -421,7 +421,7 @@ suspend inline fun CallPipeline.createWithResource ( noinline create: suspend (R) -> ResourceResult, noinline authPredicate: suspend (User, R) -> Boolean = defaultPredicateLambda, noinline doAfter: suspend (R) -> Unit = {} -) { +) = pipeline { try { val rec = call.receive() From 26ccf83aae18696102c51deb84317373a667f6d9 Mon Sep 17 00:00:00 2001 From: Hamza Date: Mon, 4 Nov 2019 18:34:55 +0500 Subject: [PATCH 15/75] Will not compile. Trying to fix conflict --- src/blogify/backend/routes/handling/Handlers.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/blogify/backend/routes/handling/Handlers.kt b/src/blogify/backend/routes/handling/Handlers.kt index 32deab82..8f4d0ef6 100644 --- a/src/blogify/backend/routes/handling/Handlers.kt +++ b/src/blogify/backend/routes/handling/Handlers.kt @@ -419,8 +419,7 @@ suspend inline fun CallPipeline.deleteOnResource ( @BlogifyDsl suspend inline fun CallPipeline.createWithResource ( noinline create: suspend (R) -> ResourceResult, - noinline authPredicate: suspend (User, R) -> Boolean = defaultPredicateLambda, - noinline doAfter: suspend (R) -> Unit = {} + noinline authPredicate: suspend (User, R) -> Boolean = defaultPredicateLambda ) = pipeline { try { From 5f11424f8504bada00de1b8774eae383a084e086 Mon Sep 17 00:00:00 2001 From: Hamza Date: Mon, 4 Nov 2019 18:37:01 +0500 Subject: [PATCH 16/75] Merge fixes (untested) --- src/blogify/backend/routes/handling/Handlers.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/blogify/backend/routes/handling/Handlers.kt b/src/blogify/backend/routes/handling/Handlers.kt index c076967e..61f3eb39 100644 --- a/src/blogify/backend/routes/handling/Handlers.kt +++ b/src/blogify/backend/routes/handling/Handlers.kt @@ -418,7 +418,8 @@ suspend inline fun CallPipeline.deleteOnResource ( @BlogifyDsl suspend inline fun CallPipeline.createWithResource ( noinline create: suspend (R) -> ResourceResult, - noinline authPredicate: suspend (User, R) -> Boolean = defaultPredicateLambda + noinline authPredicate: suspend (User, R) -> Boolean = defaultPredicateLambda, + noinline doAfter: suspend (R) -> Unit = {} ) = pipeline { try { @@ -435,7 +436,7 @@ suspend inline fun CallPipeline.createWithResource ( res.fold ( success = { call.respond(HttpStatusCode.Created) - doAfter(rec) + doAfter(received) }, failure = call::respondExceptionMessage ) From af9eced952b49b526085d5e5885d3c2774854b08 Mon Sep 17 00:00:00 2001 From: Hamza Date: Mon, 4 Nov 2019 19:27:52 +0500 Subject: [PATCH 17/75] Frontend fixes --- .../show-all-articles.component.html | 4 +- .../show-all-articles.component.scss | 6 +- .../show-all-articles.component.ts | 5 + .../single-article-box.component.html | 40 +++++++ .../single-article-box.component.scss | 108 ++++++++++++++++++ .../single-article-box.component.spec.ts | 25 ++++ .../single-article-box.component.ts | 20 ++++ src/blogify/frontend/src/proxy.conf.json | 2 +- 8 files changed, 204 insertions(+), 6 deletions(-) create mode 100644 src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.html create mode 100644 src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.scss create mode 100644 src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.spec.ts create mode 100644 src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.ts diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html index 53413c64..84ee3d16 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html @@ -11,10 +11,10 @@

{{showingSearchResults ? 'Search results' : title}}

- + - + diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss index 40f692b6..fe7b68d1 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss @@ -26,9 +26,9 @@ #header-search-pad { flex-grow: 1; - @media (min-width: 0) and (max-width: $search-icon-break) { - display: none; - } + //@media (min-width: 0) and (max-width: $search-icon-break) { + // display: none; + //} } #header-search-icon { diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts index 2ad9b992..0c8d13f5 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts @@ -25,6 +25,8 @@ export class ShowAllArticlesComponent implements OnInit { searchQuery: string; searchResults: Article[]; + showingPad = false; + constructor ( private authService: AuthService, private articleService: ArticleService, @@ -58,4 +60,7 @@ export class ShowAllArticlesComponent implements OnInit { } } + showPad() { + this.showingPad = true + } } diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.html b/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.html new file mode 100644 index 00000000..1d87c063 --- /dev/null +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.html @@ -0,0 +1,40 @@ +
+ +
+ +

{{article.title}}

+ + + +
+ +
+ + {{article.summary}} + + + {{article.createdAt | relativeTime}} +
+ +

+ Read More +

+ +
+ + +

4

+ + + + + + + No tags + +
+ +
diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.scss b/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.scss new file mode 100644 index 00000000..d416c4f1 --- /dev/null +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.scss @@ -0,0 +1,108 @@ +.article { + + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + + color: var(--card-fg); + background: var(--card-bg); + + border: none; + + $box-shadow: 0 0 6px 1px rgba(0, 0, 0, 0.20); + -webkit-box-shadow: $box-shadow; + -moz-box-shadow: $box-shadow; + box-shadow: $box-shadow;; + + padding: 1.2em 1.5em; + + margin-top: 1.25em; + + .article-first-line { + width: 100%; + + text-align: center; + + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + .header-title { + text-align: left; + } + + .article-author { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + + .author-pfp { + margin-right: .85em; + } + + .author-name { font-size: 1.7em; font-weight: 600; } + + } + + } + + .article-second-line { + width: 100%; + + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + .article-summary { font-size: 1.25em; } + .article-posted-at { font-size: 1.30em; } + } + + .article-read-more { color: var(--card-fg); } + + .article-last-line { + width: 100%; + + text-align: center; + + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + } + + .article-comments-count { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + fa-icon:first-child { margin-right: .75em; } + + align-self: flex-end; + } + + .article-no-tags, + .article-tags { + display: flex; + align-self: flex-end; + + margin-top: 1.2em; + + & > * { + font-size: 1.35em; + margin: 0 .25em; + padding: .15em .65em; + + border-radius: .3em; + + &:not(:nth-child(1)) { background-color: var(--card-ct); } + &:nth-child(1) { margin-right: 0; padding-right: 0; } + &:last-child { margin-right: 0 } + } + } + +} diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.spec.ts b/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.spec.ts new file mode 100644 index 00000000..0a3a6ebc --- /dev/null +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SingleArticleBoxComponent } from './single-article-box.component'; + +describe('SingleArticleBoxComponent', () => { + let component: SingleArticleBoxComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SingleArticleBoxComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SingleArticleBoxComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.ts b/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.ts new file mode 100644 index 00000000..629019fe --- /dev/null +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.ts @@ -0,0 +1,20 @@ +import {Component, Input, OnInit} from '@angular/core'; +import {Article} from "../../../../models/Article"; +import { faCommentAlt } from '@fortawesome/free-solid-svg-icons'; + +@Component({ + selector: 'app-single-article-box', + templateUrl: './single-article-box.component.html', + styleUrls: ['./single-article-box.component.scss'] +}) +export class SingleArticleBoxComponent implements OnInit { + + @Input() article: Article; + faCommentAlt = faCommentAlt; + + + constructor() { } + + ngOnInit() { } + +} diff --git a/src/blogify/frontend/src/proxy.conf.json b/src/blogify/frontend/src/proxy.conf.json index f4e74dd5..4772242b 100644 --- a/src/blogify/frontend/src/proxy.conf.json +++ b/src/blogify/frontend/src/proxy.conf.json @@ -1,6 +1,6 @@ { "/api": { - "target": "http://localhost:8080/", + "target": "http://172.105.28.10:8080/", "secure": false } } From cb64446967d1ee7006f1b15066bea5090b857296 Mon Sep 17 00:00:00 2001 From: benjozork Date: Mon, 4 Nov 2019 09:56:44 -0500 Subject: [PATCH 18/75] search: fixes --- .../show-all-articles/show-all-articles.component.html | 4 ++-- .../show-all-articles/show-all-articles.component.scss | 6 +++--- .../show-all-articles/show-all-articles.component.ts | 5 ----- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html index 84ee3d16..e91e3941 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html @@ -11,10 +11,10 @@

{{showingSearchResults ? 'Search results' : title}}

- + - + diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss index fe7b68d1..40f692b6 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss @@ -26,9 +26,9 @@ #header-search-pad { flex-grow: 1; - //@media (min-width: 0) and (max-width: $search-icon-break) { - // display: none; - //} + @media (min-width: 0) and (max-width: $search-icon-break) { + display: none; + } } #header-search-icon { diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts index 0c8d13f5..2ad9b992 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts @@ -25,8 +25,6 @@ export class ShowAllArticlesComponent implements OnInit { searchQuery: string; searchResults: Article[]; - showingPad = false; - constructor ( private authService: AuthService, private articleService: ArticleService, @@ -60,7 +58,4 @@ export class ShowAllArticlesComponent implements OnInit { } } - showPad() { - this.showingPad = true - } } From e2365bc671d4ee226979f6e26832ef878efb9433 Mon Sep 17 00:00:00 2001 From: Hamza Date: Tue, 5 Nov 2019 18:36:04 +0500 Subject: [PATCH 19/75] Update article UI --- .../update-article.component.html | 41 ++++++++++-- .../update-article.component.scss | 66 +++++++++++++++++++ .../update-article.component.ts | 9 +++ 3 files changed, 110 insertions(+), 6 deletions(-) diff --git a/src/blogify/frontend/src/app/components/update-article/update-article.component.html b/src/blogify/frontend/src/app/components/update-article/update-article.component.html index 0fe3e2b8..7c318bc0 100644 --- a/src/blogify/frontend/src/app/components/update-article/update-article.component.html +++ b/src/blogify/frontend/src/app/components/update-article/update-article.component.html @@ -1,8 +1,37 @@ -
- Title: - Text: - Summary: - -

{{article | json}}

+ +
+ +
+

Update Article

+ + +
+ + + Title + + + + + Summary + + + + + Content + + + + + + + + + + + + + +
diff --git a/src/blogify/frontend/src/app/components/update-article/update-article.component.scss b/src/blogify/frontend/src/app/components/update-article/update-article.component.scss index e69de29b..bdf771e9 100644 --- a/src/blogify/frontend/src/app/components/update-article/update-article.component.scss +++ b/src/blogify/frontend/src/app/components/update-article/update-article.component.scss @@ -0,0 +1,66 @@ +@import "../../../styles/mixins"; +@import "../../../styles/queries"; + +#update-article { + + @include pageContainer; + + display: flex; + flex-direction: column; + justify-content: stretch; + align-items: flex-start; + + #header-row { + width: 100%; + + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + } + + .data-row { + width: 100%; + + &:not(:first-child) { + margin-top: 1em; + } + + & span:first-child { + width: 40%; + + font-size: 1.5em; + + @media (max-width: $query-desktop) { + font-size: 1.6em; + margin-bottom: .5em; + } + } + + & input:last-child, + & textarea:last-child { + flex-grow: 1; + } + + &#submit { + flex-direction: row; + justify-content: flex-end; + * { font-size: 1.35em; } + + button { + flex-grow: 0; + margin-left: .5em; + } + } + + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: stretch; + + @media (max-width: $query-desktop) { + flex-direction: column; + } + } + +} diff --git a/src/blogify/frontend/src/app/components/update-article/update-article.component.ts b/src/blogify/frontend/src/app/components/update-article/update-article.component.ts index 53c4ad3c..747afdca 100644 --- a/src/blogify/frontend/src/app/components/update-article/update-article.component.ts +++ b/src/blogify/frontend/src/app/components/update-article/update-article.component.ts @@ -3,6 +3,8 @@ import {ActivatedRoute, Router} from '@angular/router'; import { ArticleService } from '../../services/article/article.service'; import { Article } from '../../models/Article'; import { Subscription } from 'rxjs'; +import {User} from "../../models/User"; +import {AuthService} from "../../shared/auth/auth.service"; @Component({ selector: 'app-update-article', @@ -13,11 +15,13 @@ export class UpdateArticleComponent implements OnInit { routeMapSubscription: Subscription; article: Article; + user: User; constructor( private activatedRoute: ActivatedRoute, private articleService: ArticleService, private router: Router, + private authService: AuthService, ) { } ngOnInit() { @@ -32,6 +36,7 @@ export class UpdateArticleComponent implements OnInit { console.log(this.article); }); + this.authService.userProfile.then(it => { this.user = it }) } async updateArticle() { @@ -39,4 +44,8 @@ export class UpdateArticleComponent implements OnInit { await this.articleService.updateArticle(this.article); await this.router.navigateByUrl(`/article/${this.article.uuid}`); } + + addCategory() { + this.article.categories.push({name: ''}); + } } From 249aa7ab87ffbd1482ed802b3bc51d84e193eae7 Mon Sep 17 00:00:00 2001 From: Hamza Date: Wed, 6 Nov 2019 20:27:37 +0500 Subject: [PATCH 20/75] Fix new article route --- src/blogify/frontend/src/app/app-routing.module.ts | 4 ++-- .../show-all-articles/show-all-articles.component.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/blogify/frontend/src/app/app-routing.module.ts b/src/blogify/frontend/src/app/app-routing.module.ts index e0a502a7..7f2ac4bc 100644 --- a/src/blogify/frontend/src/app/app-routing.module.ts +++ b/src/blogify/frontend/src/app/app-routing.module.ts @@ -5,7 +5,7 @@ import { LoginComponent } from './components/login/login.component'; import { ProfileComponent } from './components/profile/profile.component'; import { NewArticleComponent } from './components/newarticle/new-article.component'; import { ShowArticleComponent } from './components/show-article/show-article.component'; -import { UpdateArticleComponent } from './components/update-article/update-article.component';; +import { UpdateArticleComponent } from './components/update-article/update-article.component'; const routes: Routes = [ @@ -13,7 +13,7 @@ const routes: Routes = [ { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: 'login', component: LoginComponent }, { path: 'register', component: LoginComponent }, - { path: 'new-article', component: NewArticleComponent }, + { path: 'article/new', component: NewArticleComponent }, { path: 'profile/**', component: ProfileComponent }, { path: 'article/:uuid', component: ShowArticleComponent }, { path: 'article/update/:uuid', component: UpdateArticleComponent }, diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts index 2ad9b992..b8424503 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts @@ -50,11 +50,11 @@ export class ShowAllArticlesComponent implements OnInit { async navigateToNewArticle() { if (this.authService.userToken === '') { - const url = `/login?redirect=/new-article`; + const url = `/login?redirect=/article/new`; console.log(url); await this.router.navigateByUrl(url); } else { - await this.router.navigateByUrl('/new-article'); + await this.router.navigateByUrl('/article/new'); } } From ee3c523606224aa9b908966b4f45b8729f4fa903 Mon Sep 17 00:00:00 2001 From: Hamza Date: Wed, 6 Nov 2019 21:27:58 +0500 Subject: [PATCH 21/75] Index is updated when the article is deleted (untested, theoretically should work) --- .../backend/routes/articles/ArticleRoutes.kt | 15 ++++++++++++++- src/blogify/backend/routes/handling/Handlers.kt | 4 +++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/blogify/backend/routes/articles/ArticleRoutes.kt b/src/blogify/backend/routes/articles/ArticleRoutes.kt index 1f77243b..18ab7637 100644 --- a/src/blogify/backend/routes/articles/ArticleRoutes.kt +++ b/src/blogify/backend/routes/articles/ArticleRoutes.kt @@ -27,6 +27,7 @@ import io.ktor.response.respond import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue +import io.ktor.client.request.delete fun Route.articles() { @@ -68,7 +69,19 @@ fun Route.articles() { } delete("/{uuid}") { - deleteWithId(ArticleService::get, ArticleService::delete, authPredicate = { user, article -> article.createdBy == user }) + deleteWithId( + fetch = ArticleService::get, + delete = ArticleService::delete, + authPredicate = { user, article -> article.createdBy == user }, + doAfter = { id -> + HttpClient().use { client -> + client.delete { + url("http://es:9200/articles/_doc/$id") + }.also { println(it) } + } + // DELETE //_doc/<_id> + } + ) } patch("/{uuid}") { diff --git a/src/blogify/backend/routes/handling/Handlers.kt b/src/blogify/backend/routes/handling/Handlers.kt index 61f3eb39..164ff6ce 100644 --- a/src/blogify/backend/routes/handling/Handlers.kt +++ b/src/blogify/backend/routes/handling/Handlers.kt @@ -469,7 +469,8 @@ suspend inline fun CallPipeline.createWithResource ( suspend fun CallPipeline.deleteWithId ( fetch: suspend (ApplicationCall, UUID) -> ResourceResult, delete: suspend (UUID) -> ResourceResult<*>, - authPredicate: suspend (User, R) -> Boolean = defaultPredicateLambda + authPredicate: suspend (User, R) -> Boolean = defaultPredicateLambda, + doAfter: suspend (String) -> Unit = {} ) { call.parameters["uuid"]?.let { id -> @@ -477,6 +478,7 @@ suspend fun CallPipeline.deleteWithId ( delete.invoke(id.toUUID()).fold ( success = { call.respond(HttpStatusCode.OK) + doAfter(id) }, failure = call::respondExceptionMessage ) From 0b7e69f2209656ca35473b31788c62013606b7eb Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Wed, 6 Nov 2019 13:06:53 -0500 Subject: [PATCH 22/75] search: try routing, buggy for now --- .../show-all-articles.component.html | 2 +- .../show-all-articles.component.ts | 27 ++++++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html index e91e3941..5b6d6017 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html @@ -11,7 +11,7 @@

{{showingSearchResults ? 'Search results' : title}}

- + diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts index b8424503..1fd00258 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts @@ -1,10 +1,10 @@ import { Component, Input, OnInit } from '@angular/core'; import { Article } from '../../../models/Article'; import { AuthService } from '../../auth/auth.service'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router, UrlSegment } from '@angular/router'; import { StaticContentService } from '../../../services/static/static-content.service'; import { faArrowLeft, faPencilAlt, faSearch } from '@fortawesome/free-solid-svg-icons'; -import {ArticleService} from '../../../services/article/article.service'; +import { ArticleService } from '../../../services/article/article.service'; @Component({ selector: 'app-show-all-articles', @@ -29,12 +29,31 @@ export class ShowAllArticlesComponent implements OnInit { private authService: AuthService, private articleService: ArticleService, private staticContentService: StaticContentService, + private activatedRoute: ActivatedRoute, private router: Router ) {} - ngOnInit() {} + ngOnInit() { + this.activatedRoute.url.subscribe((it: UrlSegment[]) => { + const isSearching = it[it.length - 1].parameters['search'] != undefined; + if (isSearching) { // We are in a search page + const query = it[it.length - 1].parameters['search']; + const actualQuery = query.match(/"\w+"/) != null ? query.substring(1, query.length - 1): null; + if (actualQuery != null) { + this.searchQuery = actualQuery; + this.startSearch(); + } + } else { // we are in a regular listing + this.stopSearch(); + } + }) + } + + async navigateToSearch() { + await this.router.navigate([{search: `"${this.searchQuery}"`}]) + } - async startSearch() { + private async startSearch() { this.articleService.search ( this.searchQuery, ['title', 'summary', 'createdBy', 'categories', 'createdAt'] From b4dc82e3472be6598b7823266eae11119d8fd385 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Wed, 6 Nov 2019 16:45:22 -0500 Subject: [PATCH 23/75] search: fixed routing on nested SAA pages --- .../show-all-articles/show-all-articles.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts index 1fd00258..e8b8836c 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts @@ -43,14 +43,14 @@ export class ShowAllArticlesComponent implements OnInit { this.searchQuery = actualQuery; this.startSearch(); } - } else { // we are in a regular listing + } else { // We are in a regular listing this.stopSearch(); } }) } async navigateToSearch() { - await this.router.navigate([{search: `"${this.searchQuery}"`}]) + await this.router.navigate([{ search: `"${this.searchQuery}"` }], { relativeTo: this.activatedRoute }) } private async startSearch() { From 8c1a377bb4ea55477a80a7c38d4691bad4a3f1d3 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Wed, 6 Nov 2019 16:47:10 -0500 Subject: [PATCH 24/75] SAA: fix missing border on container --- .../single-article-box/single-article-box.component.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.scss b/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.scss index d416c4f1..2c3df211 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.scss +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.scss @@ -1,3 +1,5 @@ +@import "../../../../../styles/layouts"; + .article { display: flex; @@ -9,6 +11,7 @@ background: var(--card-bg); border: none; + border-radius: $std-border-radius; $box-shadow: 0 0 6px 1px rgba(0, 0, 0, 0.20); -webkit-box-shadow: $box-shadow; From f3e3eb4d23d4b58e44cb162b3b5bb848e04d1702 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Wed, 6 Nov 2019 17:17:59 -0500 Subject: [PATCH 25/75] search: fix routerLink in SAA search result user-display components --- .../shared/components/user-display/user-display.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blogify/frontend/src/app/shared/components/user-display/user-display.component.html b/src/blogify/frontend/src/app/shared/components/user-display/user-display.component.html index b41dcbaa..7e2a7ffe 100644 --- a/src/blogify/frontend/src/app/shared/components/user-display/user-display.component.html +++ b/src/blogify/frontend/src/app/shared/components/user-display/user-display.component.html @@ -1,6 +1,6 @@ + [routerLink]="['/profile/' + user.username]"> {{infoText}} From 38566f4f33af38a6f97e08d1be4ef413d0a8189a Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Wed, 6 Nov 2019 17:18:03 -0500 Subject: [PATCH 26/75] consistency --- .../components/show-all-articles/show-all-articles.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts index e8b8836c..9fea7f18 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts @@ -68,7 +68,7 @@ export class ShowAllArticlesComponent implements OnInit { } async navigateToNewArticle() { - if (this.authService.userToken === '') { + if (!this.authService.isLoggedIn()) { const url = `/login?redirect=/article/new`; console.log(url); await this.router.navigateByUrl(url); From 9537b41dddf4d82598295581139068cafba86542 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Wed, 6 Nov 2019 17:18:08 -0500 Subject: [PATCH 27/75] SAA: fix missing border on container --- .../frontend/src/app/models/Article.ts | 6 ++- .../app/services/article/article.service.ts | 43 ++++++++++--------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/blogify/frontend/src/app/models/Article.ts b/src/blogify/frontend/src/app/models/Article.ts index 4f42cc4c..084153a7 100644 --- a/src/blogify/frontend/src/app/models/Article.ts +++ b/src/blogify/frontend/src/app/models/Article.ts @@ -1,15 +1,17 @@ import { User } from './User'; export class Article { - constructor( + + constructor ( public uuid: string, public title: string, public content: string, public summary: string, - public createdBy: User, + public createdBy: User | string, public createdAt: number, public categories: Category[], ) {} + } export interface Category { diff --git a/src/blogify/frontend/src/app/services/article/article.service.ts b/src/blogify/frontend/src/app/services/article/article.service.ts index 05703953..21576673 100644 --- a/src/blogify/frontend/src/app/services/article/article.service.ts +++ b/src/blogify/frontend/src/app/services/article/article.service.ts @@ -1,9 +1,8 @@ -import { Injectable } from '@angular/core'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { Article } from '../../models/Article'; -import { AuthService } from '../../shared/auth/auth.service'; +import {Injectable} from '@angular/core'; +import {HttpClient, HttpHeaders} from '@angular/common/http'; +import {Article} from '../../models/Article'; +import {AuthService} from '../../shared/auth/auth.service'; import * as uuid from 'uuid/v4'; -import {consoleTestResultHandler} from "tslint/lib/test"; @Injectable({ providedIn: 'root' @@ -16,22 +15,21 @@ export class ArticleService { async getAllArticles(fields: string[] = [], amount: number = 25): Promise { const articlesObs = this.httpClient.get(`/api/articles/?fields=${fields.join(',')}&amount=${amount}`); const articles = await articlesObs.toPromise(); - return this.uuidToObjectInArticle(articles) + return this.fetchUserObjects(articles) } - private async uuidToObjectInArticle(articles: Article[]) { - const userUUIDs = new Set(); - articles.forEach(it => { - userUUIDs.add(it.createdBy.toString()); + private async fetchUserObjects(articles: Article[]) { + const userUUIDs = articles + .filter (it => typeof it.createdBy === 'string') + .map (it => it.createdBy); + const userObjects = await Promise.all ( + [...userUUIDs].map(it => this.authService.fetchUser(it)) + ); + return articles.map(a => { + a.createdBy = userObjects + .find(u => u.uuid === a.createdBy); + return a }); - const users = await Promise.all(([...userUUIDs]).map(it => this.authService.fetchUser(it.toString()))); - const out: Article[] = []; - articles.forEach((article) => { - const copy = article; - copy.createdBy = users.find((user) => user.uuid === article.createdBy.toString()); - out.push(copy); - }); - return out; } async getArticleByUUID(uuid: string, fields: string[] = []): Promise
{ @@ -45,7 +43,7 @@ export class ArticleService { async getArticleByForUser(username: string, fields: string[] = []): Promise { const articles = await this.httpClient.get(`/api/articles/forUser/${username}?fields=${fields.join(',')}`).toPromise(); - return this.uuidToObjectInArticle(articles); + return this.fetchUserObjects(articles); } async createNewArticle(article: Article, userToken: string = this.authService.userToken): Promise { @@ -85,7 +83,7 @@ export class ArticleService { title: article.title, summary: article.summary, categories: article.categories, - createdBy: article.createdBy.uuid, + createdBy: (typeof article.createdBy === 'string') ? article.createdBy : article.createdBy.uuid, }; return this.httpClient.patch
(`/api/articles/${uuid}`, newArticle, httpOptions).toPromise(); @@ -104,6 +102,9 @@ export class ArticleService { search(query: string, fields: string[]) { const url = `/api/articles/search/?q=${query}&fields=${fields.join(',')}`; - return this.httpClient.get(url).toPromise(); + return this.httpClient.get(url) + .toPromise() + .then(articles => this.fetchUserObjects(articles)); // Make sure user data is present } + } From 80957bb310fda86ec21d362da4a997fe64ef4325 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Wed, 6 Nov 2019 17:18:08 -0500 Subject: [PATCH 28/75] SAA: inject user objects on search results --- .../frontend/src/app/models/Article.ts | 6 ++- .../app/services/article/article.service.ts | 43 ++++++++++--------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/blogify/frontend/src/app/models/Article.ts b/src/blogify/frontend/src/app/models/Article.ts index 4f42cc4c..084153a7 100644 --- a/src/blogify/frontend/src/app/models/Article.ts +++ b/src/blogify/frontend/src/app/models/Article.ts @@ -1,15 +1,17 @@ import { User } from './User'; export class Article { - constructor( + + constructor ( public uuid: string, public title: string, public content: string, public summary: string, - public createdBy: User, + public createdBy: User | string, public createdAt: number, public categories: Category[], ) {} + } export interface Category { diff --git a/src/blogify/frontend/src/app/services/article/article.service.ts b/src/blogify/frontend/src/app/services/article/article.service.ts index 05703953..21576673 100644 --- a/src/blogify/frontend/src/app/services/article/article.service.ts +++ b/src/blogify/frontend/src/app/services/article/article.service.ts @@ -1,9 +1,8 @@ -import { Injectable } from '@angular/core'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { Article } from '../../models/Article'; -import { AuthService } from '../../shared/auth/auth.service'; +import {Injectable} from '@angular/core'; +import {HttpClient, HttpHeaders} from '@angular/common/http'; +import {Article} from '../../models/Article'; +import {AuthService} from '../../shared/auth/auth.service'; import * as uuid from 'uuid/v4'; -import {consoleTestResultHandler} from "tslint/lib/test"; @Injectable({ providedIn: 'root' @@ -16,22 +15,21 @@ export class ArticleService { async getAllArticles(fields: string[] = [], amount: number = 25): Promise { const articlesObs = this.httpClient.get(`/api/articles/?fields=${fields.join(',')}&amount=${amount}`); const articles = await articlesObs.toPromise(); - return this.uuidToObjectInArticle(articles) + return this.fetchUserObjects(articles) } - private async uuidToObjectInArticle(articles: Article[]) { - const userUUIDs = new Set(); - articles.forEach(it => { - userUUIDs.add(it.createdBy.toString()); + private async fetchUserObjects(articles: Article[]) { + const userUUIDs = articles + .filter (it => typeof it.createdBy === 'string') + .map (it => it.createdBy); + const userObjects = await Promise.all ( + [...userUUIDs].map(it => this.authService.fetchUser(it)) + ); + return articles.map(a => { + a.createdBy = userObjects + .find(u => u.uuid === a.createdBy); + return a }); - const users = await Promise.all(([...userUUIDs]).map(it => this.authService.fetchUser(it.toString()))); - const out: Article[] = []; - articles.forEach((article) => { - const copy = article; - copy.createdBy = users.find((user) => user.uuid === article.createdBy.toString()); - out.push(copy); - }); - return out; } async getArticleByUUID(uuid: string, fields: string[] = []): Promise
{ @@ -45,7 +43,7 @@ export class ArticleService { async getArticleByForUser(username: string, fields: string[] = []): Promise { const articles = await this.httpClient.get(`/api/articles/forUser/${username}?fields=${fields.join(',')}`).toPromise(); - return this.uuidToObjectInArticle(articles); + return this.fetchUserObjects(articles); } async createNewArticle(article: Article, userToken: string = this.authService.userToken): Promise { @@ -85,7 +83,7 @@ export class ArticleService { title: article.title, summary: article.summary, categories: article.categories, - createdBy: article.createdBy.uuid, + createdBy: (typeof article.createdBy === 'string') ? article.createdBy : article.createdBy.uuid, }; return this.httpClient.patch
(`/api/articles/${uuid}`, newArticle, httpOptions).toPromise(); @@ -104,6 +102,9 @@ export class ArticleService { search(query: string, fields: string[]) { const url = `/api/articles/search/?q=${query}&fields=${fields.join(',')}`; - return this.httpClient.get(url).toPromise(); + return this.httpClient.get(url) + .toPromise() + .then(articles => this.fetchUserObjects(articles)); // Make sure user data is present } + } From 0e93d87f2d0a2e7dca87fb2159d77d349adba4bf Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Wed, 6 Nov 2019 17:19:29 -0500 Subject: [PATCH 29/75] formatting --- .../frontend/src/app/services/article/article.service.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/blogify/frontend/src/app/services/article/article.service.ts b/src/blogify/frontend/src/app/services/article/article.service.ts index 21576673..8e984d6f 100644 --- a/src/blogify/frontend/src/app/services/article/article.service.ts +++ b/src/blogify/frontend/src/app/services/article/article.service.ts @@ -1,7 +1,7 @@ -import {Injectable} from '@angular/core'; -import {HttpClient, HttpHeaders} from '@angular/common/http'; -import {Article} from '../../models/Article'; -import {AuthService} from '../../shared/auth/auth.service'; +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders} from '@angular/common/http'; +import { Article } from '../../models/Article'; +import { AuthService } from '../../shared/auth/auth.service'; import * as uuid from 'uuid/v4'; @Injectable({ From 8fb5a35298682ec7b0f3424b3842d19885fa664c Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Wed, 6 Nov 2019 21:40:14 -0500 Subject: [PATCH 30/75] fix --- .../src/app/components/show-article/show-article.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/blogify/frontend/src/app/components/show-article/show-article.component.ts b/src/blogify/frontend/src/app/components/show-article/show-article.component.ts index 78c7c8d4..346adc2a 100644 --- a/src/blogify/frontend/src/app/components/show-article/show-article.component.ts +++ b/src/blogify/frontend/src/app/components/show-article/show-article.component.ts @@ -37,8 +37,8 @@ export class ShowArticleComponent implements OnInit { ['title', 'createdBy', 'content', 'summary', 'uuid', 'categories', 'createdAt'] ); - this.showUpdateButton = (await this.authService.userUUID) == this.article.createdBy.uuid; - this.showDeleteButton = (await this.authService.userUUID) == this.article.createdBy.uuid; + this.showUpdateButton = (await this.authService.userUUID) == ( this.article.createdBy).uuid; + this.showDeleteButton = (await this.authService.userUUID) == ( this.article.createdBy).uuid; console.log(this.article); }); From bab3c5b050fec763e27222dfdd829572c1fa314e Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Wed, 6 Nov 2019 22:19:39 -0500 Subject: [PATCH 31/75] auth-fe: made isLoggedIn() an Observable and fixed token attempt logic --- .../create-comment.component.ts | 16 ++++--- .../single-comment.component.ts | 4 +- .../components/navbar/navbar.component.html | 12 ++--- .../profile/main/main-profile.component.ts | 2 +- .../src/app/shared/auth/auth.service.ts | 47 ++++++++++++++----- .../show-all-articles.component.ts | 2 +- 6 files changed, 55 insertions(+), 28 deletions(-) diff --git a/src/blogify/frontend/src/app/components/comment/create-comment/create-comment.component.ts b/src/blogify/frontend/src/app/components/comment/create-comment/create-comment.component.ts index 18e6cde8..4bfd091c 100644 --- a/src/blogify/frontend/src/app/components/comment/create-comment/create-comment.component.ts +++ b/src/blogify/frontend/src/app/components/comment/create-comment/create-comment.component.ts @@ -23,17 +23,19 @@ export class CreateCommentComponent implements OnInit { constructor(private commentsService: CommentsService, private authService: AuthService) {} async ngOnInit() { - this.replyComment = { - commenter: this.authService.isLoggedIn() ? await this.authService.userProfile : '', - article: this.comment === undefined ? this.article : this.comment.article, - content: '', - uuid: '' - }; + this.authService.observeIsLoggedIn().subscribe(async value => { + this.replyComment = { + commenter: value ? await this.authService.userProfile : '', + article: this.comment === undefined ? this.article : this.comment.article, + content: '', + uuid: '' + }; + }); } async doReply() { // Make sure the user is authenticated - if (this.authService.isLoggedIn() && this.replyComment.commenter instanceof User) { + if (this.authService.observeIsLoggedIn() && this.replyComment.commenter instanceof User) { if (this.comment === undefined) { // Reply to article await this.commentsService.createComment ( diff --git a/src/blogify/frontend/src/app/components/comment/single-comment/single-comment.component.ts b/src/blogify/frontend/src/app/components/comment/single-comment/single-comment.component.ts index 1eac3776..d2e9f2a7 100644 --- a/src/blogify/frontend/src/app/components/comment/single-comment/single-comment.component.ts +++ b/src/blogify/frontend/src/app/components/comment/single-comment/single-comment.component.ts @@ -44,7 +44,7 @@ export class SingleCommentComponent implements OnInit { // We're ready, so we can populate the dummy reply comment this.replyComment = { - commenter: this.authService.isLoggedIn() ? await this.authService.userProfile : '', + commenter: await this.authService.observeIsLoggedIn() ? await this.authService.userProfile : '', article: this.comment.article, content: '', uuid: '' @@ -58,7 +58,7 @@ export class SingleCommentComponent implements OnInit { console.log(this.replyComment.commenter instanceof User); // Make sure the user is authenticated - if (this.authService.isLoggedIn() && this.replyComment.commenter instanceof User) { + if (this.authService.observeIsLoggedIn() && this.replyComment.commenter instanceof User) { await this.commentsService.replyToComment ( this.replyComment.content, this.comment.article.uuid, diff --git a/src/blogify/frontend/src/app/components/navbar/navbar.component.html b/src/blogify/frontend/src/app/components/navbar/navbar.component.html index 5d4fbfea..69d7a2a9 100644 --- a/src/blogify/frontend/src/app/components/navbar/navbar.component.html +++ b/src/blogify/frontend/src/app/components/navbar/navbar.component.html @@ -20,27 +20,27 @@ - + - + {{user.name}} - + - + - + Login - + diff --git a/src/blogify/frontend/src/app/components/profile/profile/main/main-profile.component.ts b/src/blogify/frontend/src/app/components/profile/profile/main/main-profile.component.ts index 85700dd9..3b2bdd39 100644 --- a/src/blogify/frontend/src/app/components/profile/profile/main/main-profile.component.ts +++ b/src/blogify/frontend/src/app/components/profile/profile/main/main-profile.component.ts @@ -33,7 +33,7 @@ export class MainProfileComponent implements OnInit { this.route.params.subscribe(async (params: Params) => { let username = params['username']; - if (this.authService.isLoggedIn()) { + if (await this.authService.observeIsLoggedIn()) { const loggedInUsername = (await this.authService.userProfile).username; if (username === loggedInUsername) { diff --git a/src/blogify/frontend/src/app/shared/auth/auth.service.ts b/src/blogify/frontend/src/app/shared/auth/auth.service.ts index 7d474107..fb3e6b84 100644 --- a/src/blogify/frontend/src/app/shared/auth/auth.service.ts +++ b/src/blogify/frontend/src/app/shared/auth/auth.service.ts @@ -4,6 +4,7 @@ import { LoginCredentials, RegisterCredentials, User } from 'src/app/models/User import { BehaviorSubject, Observable } from 'rxjs'; import { StaticFile } from '../../models/Static'; import { StaticContentService } from '../../services/static/static-content.service'; +import {Router} from '@angular/router'; @Injectable({ providedIn: 'root' @@ -14,24 +15,46 @@ export class AuthService { private currentUserUuid_ = new BehaviorSubject(''); private currentUser_ = new BehaviorSubject(this.dummyUser); + private loginObservable_ = new BehaviorSubject(false); + + constructor ( + private httpClient: HttpClient, + private staticContentService: StaticContentService, + private router: Router + ) { + this.attemptRestoreLogin() + } - constructor(private httpClient: HttpClient, private staticContentService: StaticContentService) { - if (localStorage.getItem('userToken') !== null) { - this.login(localStorage.getItem('userToken')) + private attemptRestoreLogin() { + const token = AuthService.attemptFindLocalToken(); + if (token == null) { + console.info('[blogifyAuth] No stored token, asking user for login'); + this.router.navigateByUrl('/login?redirect=/home') + } else { + this.login(token).then ( + (res) => { + console.info('[blogifyAuth] Logged in with stored token') + }, (err) => { + console.error('[blogifyAuth] Error while attempting stored token, not logging in.') + }); } } + private static attemptFindLocalToken(): string | null { + return localStorage.getItem('userToken'); + } + async login(creds: LoginCredentials | string): Promise { let token: Observable; let it: UserToken; - if (typeof creds !== 'string') { + if (typeof creds !== 'string') { // user / password token = this.httpClient.post('/api/auth/signin', creds, { responseType: 'json' }); it = await token.toPromise(); localStorage.setItem('userToken', it.token); - } else { + } else { // token it = { token: creds } } @@ -43,16 +66,22 @@ export class AuthService { this.currentUser_.next(fetchedUser); this.currentUserUuid_.next(fetchedUser.uuid); + this.loginObservable_.next(true); return it } + logout() { + localStorage.removeItem("userToken"); + this.loginObservable_.next(false); + } + async register(credentials: RegisterCredentials): Promise { return this.httpClient.post('/api/auth/signup', credentials).toPromise(); } - isLoggedIn(): boolean { - return this.userToken !== null; + observeIsLoggedIn(): Observable { + return this.loginObservable_; } private async getUserUUIDFromToken(token: string): Promise { @@ -86,10 +115,6 @@ export class AuthService { return this.getUser() } - logout() { - localStorage.removeItem("userToken") - } - private async getUser(): Promise { if (this.currentUser_.getValue().uuid != '') { return this.currentUser_.getValue() diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts index 9fea7f18..5899dc43 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts @@ -68,7 +68,7 @@ export class ShowAllArticlesComponent implements OnInit { } async navigateToNewArticle() { - if (!this.authService.isLoggedIn()) { + if (! await this.authService.observeIsLoggedIn().toPromise()) { const url = `/login?redirect=/article/new`; console.log(url); await this.router.navigateByUrl(url); From dbf83b8916770f8be04b211541728f44d6c7041f Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Thu, 7 Nov 2019 20:45:31 -0500 Subject: [PATCH 32/75] auth-fe: don't redirect when no token in ls --- src/blogify/frontend/src/app/shared/auth/auth.service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/blogify/frontend/src/app/shared/auth/auth.service.ts b/src/blogify/frontend/src/app/shared/auth/auth.service.ts index fb3e6b84..478638fd 100644 --- a/src/blogify/frontend/src/app/shared/auth/auth.service.ts +++ b/src/blogify/frontend/src/app/shared/auth/auth.service.ts @@ -28,14 +28,14 @@ export class AuthService { private attemptRestoreLogin() { const token = AuthService.attemptFindLocalToken(); if (token == null) { - console.info('[blogifyAuth] No stored token, asking user for login'); - this.router.navigateByUrl('/login?redirect=/home') + console.info('[blogifyAuth] No stored token'); } else { this.login(token).then ( (res) => { console.info('[blogifyAuth] Logged in with stored token') }, (err) => { - console.error('[blogifyAuth] Error while attempting stored token, not logging in.') + console.error('[blogifyAuth] Error while attempting stored token, not logging in and clearing token.') + localStorage.removeItem('userToken'); }); } } From 3102852ad1bff8a86556e9fe5356db2574540f52 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Thu, 7 Nov 2019 21:25:48 -0500 Subject: [PATCH 33/75] auth-fe: more fixes for login state woes --- .../components/navbar/navbar.component.html | 36 +------------------ .../app/components/navbar/navbar.component.ts | 11 ++++-- .../profile/main/main-profile.component.ts | 4 +-- .../src/app/shared/auth/auth.service.ts | 2 +- 4 files changed, 12 insertions(+), 41 deletions(-) diff --git a/src/blogify/frontend/src/app/components/navbar/navbar.component.html b/src/blogify/frontend/src/app/components/navbar/navbar.component.html index 69d7a2a9..15c71f9d 100644 --- a/src/blogify/frontend/src/app/components/navbar/navbar.component.html +++ b/src/blogify/frontend/src/app/components/navbar/navbar.component.html @@ -11,19 +11,11 @@ - - - + - {{user.name}} @@ -48,32 +40,6 @@ - - diff --git a/src/blogify/frontend/src/app/components/navbar/navbar.component.ts b/src/blogify/frontend/src/app/components/navbar/navbar.component.ts index 9d8721d6..4b4f7b0f 100644 --- a/src/blogify/frontend/src/app/components/navbar/navbar.component.ts +++ b/src/blogify/frontend/src/app/components/navbar/navbar.component.ts @@ -25,9 +25,14 @@ export class NavbarComponent implements OnInit, AfterViewInit { private darkModeService: DarkModeService, ) {} - async ngOnInit() { - this.user = await this.authService.userProfile; - console.log(this.user.profilePicture); + ngOnInit() { + this.authService.observeIsLoggedIn().subscribe(async value => { + if (value) { + this.user = await this.authService.userProfile; + } else { + this.user = undefined; + } + }); } ngAfterViewInit() { diff --git a/src/blogify/frontend/src/app/components/profile/profile/main/main-profile.component.ts b/src/blogify/frontend/src/app/components/profile/profile/main/main-profile.component.ts index 3b2bdd39..33e919da 100644 --- a/src/blogify/frontend/src/app/components/profile/profile/main/main-profile.component.ts +++ b/src/blogify/frontend/src/app/components/profile/profile/main/main-profile.component.ts @@ -33,13 +33,13 @@ export class MainProfileComponent implements OnInit { this.route.params.subscribe(async (params: Params) => { let username = params['username']; - if (await this.authService.observeIsLoggedIn()) { + this.authService.observeIsLoggedIn().subscribe(async value => { const loggedInUsername = (await this.authService.userProfile).username; if (username === loggedInUsername) { this.finalTabs = this.baseTabs.concat(this.loggedInTabs); } - } + }); this.user = await this.authService.getByUsername(username); }) diff --git a/src/blogify/frontend/src/app/shared/auth/auth.service.ts b/src/blogify/frontend/src/app/shared/auth/auth.service.ts index 478638fd..0cee8260 100644 --- a/src/blogify/frontend/src/app/shared/auth/auth.service.ts +++ b/src/blogify/frontend/src/app/shared/auth/auth.service.ts @@ -34,7 +34,7 @@ export class AuthService { (res) => { console.info('[blogifyAuth] Logged in with stored token') }, (err) => { - console.error('[blogifyAuth] Error while attempting stored token, not logging in and clearing token.') + console.error('[blogifyAuth] Error while attempting stored token, not logging in and clearing token.'); localStorage.removeItem('userToken'); }); } From 3f5082a3269c75d0ea6f01c9739307f7afb59970 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Sat, 9 Nov 2019 16:12:53 -0500 Subject: [PATCH 34/75] validation: added validation endpoint and utility functions in backend --- src/blogify/backend/annotations/check.kt | 2 +- .../backend/resources/slicing/Mapper.kt | 17 +++++++++ .../backend/resources/slicing/Verifier.kt | 13 ++++--- .../backend/routes/articles/ArticleRoutes.kt | 7 +++- .../backend/routes/handling/Handlers.kt | 22 ++++++++++- src/blogify/backend/util/Collections.kt | 38 +++++++++---------- 6 files changed, 69 insertions(+), 30 deletions(-) diff --git a/src/blogify/backend/annotations/check.kt b/src/blogify/backend/annotations/check.kt index 13e281c9..9768f0cf 100644 --- a/src/blogify/backend/annotations/check.kt +++ b/src/blogify/backend/annotations/check.kt @@ -8,4 +8,4 @@ import org.intellij.lang.annotations.Language @MustBeDocumented annotation class check ( @Language(value = "RegExp") val pattern: String -) \ No newline at end of file +) diff --git a/src/blogify/backend/resources/slicing/Mapper.kt b/src/blogify/backend/resources/slicing/Mapper.kt index 23c6d735..793acc6a 100644 --- a/src/blogify/backend/resources/slicing/Mapper.kt +++ b/src/blogify/backend/resources/slicing/Mapper.kt @@ -3,6 +3,7 @@ package blogify.backend.resources.slicing import blogify.backend.annotations.check import blogify.backend.annotations.noslice import blogify.backend.resources.slicing.models.Mapped +import blogify.backend.util.filterThenMapValues import com.andreapivetta.kolor.green @@ -107,6 +108,15 @@ fun M.cachedPropMap(): PropMap { return cached } +/** + * Fetches (or computes if the class is not in the cache) a [property map][PropMap] for the reciever [KClass] + * + * @receiver the [class][KClass] for which the [PropMap] should be obtained + * + * @return the obtained [PropMap] + * + * @author Benjozork + */ fun KClass.cachedPropMap(): PropMap { var cached: PropMap? = propMapCache[this] if (cached == null) { @@ -116,3 +126,10 @@ fun KClass.cachedPropMap(): PropMap { return cached } + +/** + * Returns a PropMap containing only handles that are [ok][PropertyHandle.Ok] + * + * @author Benjozork + */ +fun PropMap.okHandles() = this.filterThenMapValues({ it is PropertyHandle.Ok }, { it.value as PropertyHandle.Ok }) diff --git a/src/blogify/backend/resources/slicing/Verifier.kt b/src/blogify/backend/resources/slicing/Verifier.kt index 1549fadb..92ca1ef9 100644 --- a/src/blogify/backend/resources/slicing/Verifier.kt +++ b/src/blogify/backend/resources/slicing/Verifier.kt @@ -1,6 +1,7 @@ package blogify.backend.resources.slicing import blogify.backend.resources.slicing.models.Mapped +import blogify.backend.util.filterThenMapValues /** * Verifies that a [Mapped] object's [String] properties conform to @@ -8,9 +9,9 @@ import blogify.backend.resources.slicing.models.Mapped * * @author Benjozork */ -fun Mapped.verify(): Map = this.cachedPropMap() - .filterValues { it is PropertyHandle.Ok } - .mapValues { it.value as PropertyHandle.Ok } - .filterValues { it.property.returnType.classifier == String::class } - .map { it.value to (it.value.regexCheck?.let { regex -> (it.value.property.get(this) as String).matches(regex) } ?: true) } - .toMap() +fun Mapped.verify(): Map = this.cachedPropMap().okHandles() + .mapKeys { it.value } // Use property handles as keys + .filterThenMapValues ( + { it.property.returnType.classifier == String::class }, + { (it.value.regexCheck?.let { regex -> (it.value.property.get(this) as String).matches(regex) } ?: true) } + ) diff --git a/src/blogify/backend/routes/articles/ArticleRoutes.kt b/src/blogify/backend/routes/articles/ArticleRoutes.kt index 18ab7637..f00a54d3 100644 --- a/src/blogify/backend/routes/articles/ArticleRoutes.kt +++ b/src/blogify/backend/routes/articles/ArticleRoutes.kt @@ -19,6 +19,7 @@ import io.ktor.routing.* import io.ktor.client.HttpClient import io.ktor.client.request.get import io.ktor.client.request.post +import io.ktor.client.request.delete import io.ktor.client.request.url import io.ktor.client.features.json.JsonFeature import io.ktor.content.TextContent @@ -26,8 +27,6 @@ import io.ktor.http.ContentType import io.ktor.response.respond import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue -import io.ktor.client.request.delete fun Route.articles() { @@ -133,6 +132,10 @@ fun Route.articles() { } } + get("_validations") { + getValidations
() + } + articleComments() } diff --git a/src/blogify/backend/routes/handling/Handlers.kt b/src/blogify/backend/routes/handling/Handlers.kt index 164ff6ce..d8f220f7 100644 --- a/src/blogify/backend/routes/handling/Handlers.kt +++ b/src/blogify/backend/routes/handling/Handlers.kt @@ -44,12 +44,15 @@ import blogify.backend.services.models.ResourceResultSet import blogify.backend.services.models.Service import blogify.backend.annotations.BlogifyDsl import blogify.backend.annotations.type +import blogify.backend.resources.slicing.models.Mapped +import blogify.backend.resources.slicing.okHandles import blogify.backend.resources.slicing.verify import blogify.backend.routes.pipelines.CallPipeLineFunction import blogify.backend.routes.pipelines.CallPipeline import blogify.backend.routes.pipelines.handleAuthentication import blogify.backend.routes.pipelines.pipeline import blogify.backend.routes.pipelines.pipelineError +import blogify.backend.util.filterThenMapValues import blogify.backend.util.getOrPipelineError import blogify.backend.util.letCatchingOrNull import blogify.backend.util.matches @@ -383,7 +386,6 @@ suspend inline fun CallPipeline.deleteOnResource ( Uploadables.select { Uploadables.fileId eq uploadableId }.single() }.map { Uploadables.convert(call, it).get() }.get() - // Delete in DB query { Uploadables.deleteWhere { Uploadables.fileId eq uploadableId } @@ -497,6 +499,7 @@ suspend fun CallPipeline.deleteWithId ( } ?: call.respond(HttpStatusCode.BadRequest) } + /** * Adds a handler to a [CallPipeline] that handles updating a resource with the given uuid. * @@ -541,3 +544,20 @@ suspend inline fun CallPipeline.updateWithId ( ) } + +/** + * Adds a handler to a [CallPipeline] that returns the validation regexps for a certain class. + * + * @param M the class for which to return validations + * + * @author Benjozork + */ +suspend inline fun CallPipeline.getValidations() { + call.respond ( + M::class.cachedPropMap().okHandles() + .filterThenMapValues ( + { it.regexCheck != null }, + { it.value.regexCheck!!.pattern } + ) + ) +} diff --git a/src/blogify/backend/util/Collections.kt b/src/blogify/backend/util/Collections.kt index 7ba3e52a..6fbd104e 100644 --- a/src/blogify/backend/util/Collections.kt +++ b/src/blogify/backend/util/Collections.kt @@ -1,24 +1,5 @@ package blogify.backend.util -fun Iterable.singleOrNullOrError(): T? { - when (this) { - is List -> return when { - size == 1 -> this[0] - size > 1 -> error("Collection has multiple elements") - else -> null - } - else -> { - val iterator = iterator() - if (!iterator.hasNext()) - return null - val single = iterator.next() - if (iterator.hasNext()) - error("Collection has multiple elements") - return single - } - } -} - /** * Allows to specify a function to execute depending on whether a collection has exactly one item, multiple items or no items. */ @@ -37,4 +18,21 @@ suspend fun Iterable.foldForOne ( } else { one(e) } -} \ No newline at end of file +} + +fun Collection.filterMap(predicate: (T) -> Boolean, mapper: (T) -> R): Collection { + return this.filter(predicate).map(mapper) +} + +fun Map.filterThenMapKeys ( + predicate: (K) -> Boolean, + mapper: (Map.Entry) -> R +): Map { + return this.filterKeys(predicate).mapKeys(mapper) +} +fun Map.filterThenMapValues ( + predicate: (V) -> Boolean, + mapper: (Map.Entry) -> R +): Map { + return this.filterValues(predicate).mapValues(mapper) +} From ae0c9c7f23c8d035ae8d4a6c526e3fbad406bc34 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Sat, 9 Nov 2019 17:30:15 -0500 Subject: [PATCH 35/75] validation: made new-article a form --- src/blogify/frontend/src/app/app.module.ts | 5 ++- .../newarticle/new-article.component.html | 18 ++++++----- .../newarticle/new-article.component.ts | 31 +++++++++++++++++-- src/blogify/frontend/src/styles/forms.scss | 10 ++++++ 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/blogify/frontend/src/app/app.module.ts b/src/blogify/frontend/src/app/app.module.ts index 9deea9de..191064c1 100644 --- a/src/blogify/frontend/src/app/app.module.ts +++ b/src/blogify/frontend/src/app/app.module.ts @@ -1,7 +1,7 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { HttpClientModule } from '@angular/common/http'; -import { FormsModule } from '@angular/forms'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { LoginComponent } from './components/login/login.component'; @@ -12,8 +12,6 @@ import { NewArticleComponent } from './components/newarticle/new-article.compone import { ShowArticleComponent } from './components/show-article/show-article.component'; import { ArticleCommentsComponent } from './components/comment/article-comments.component'; import { NavbarComponent } from './components/navbar/navbar.component'; -import { DarkThemeDirective } from './shared/directives/dark-theme/dark-theme.directive'; -import { CompactDirective } from './shared/directives/compact/compact.directive'; import { SingleCommentComponent } from './components/comment/single-comment/single-comment.component'; import { CreateCommentComponent } from './components/comment/create-comment/create-comment.component'; import { UpdateArticleComponent } from './components/update-article/update-article.component'; @@ -42,6 +40,7 @@ import { ProfileModule } from './components/profile/profile/profile.module'; AppRoutingModule, HttpClientModule, FormsModule, + ReactiveFormsModule, FontAwesomeModule, MarkdownModule.forRoot(), ProfileModule, diff --git a/src/blogify/frontend/src/app/components/newarticle/new-article.component.html b/src/blogify/frontend/src/app/components/newarticle/new-article.component.html index 22dacfb6..a87ac271 100644 --- a/src/blogify/frontend/src/app/components/newarticle/new-article.component.html +++ b/src/blogify/frontend/src/app/components/newarticle/new-article.component.html @@ -1,5 +1,5 @@ -
+

New Article

@@ -9,26 +9,28 @@

New Article

Title - + Summary - + Content - + - - + + - - + +
+ +
diff --git a/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts b/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts index af03ce62..f03d6ac9 100644 --- a/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts +++ b/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts @@ -4,6 +4,8 @@ import { ArticleService } from '../../services/article/article.service'; import { User } from '../../models/User'; import { StaticFile } from "../../models/Static"; import { AuthService } from '../../shared/auth/auth.service'; +import {AbstractControl, FormArray, FormControl, FormGroup, Validators} from '@angular/forms'; +import {DatePipe} from '@angular/common'; @Component({ selector: 'app-new-article', @@ -23,19 +25,42 @@ export class NewArticleComponent implements OnInit { }; user: User; + datePipe = new DatePipe('en-US'); + + formTitle = new FormControl('', [Validators.required]); + formSummary = new FormControl('', [Validators.required]); + formContent = new FormControl('', [Validators.required]); + formCategories = new FormArray([new FormControl('')]); + + form = new FormGroup({ + 'title': this.formTitle, + 'summary': this.formSummary, + 'content': this.formContent, + 'categories': this.formCategories + }); + constructor(private articleService: ArticleService, private authService: AuthService) {} async ngOnInit() { this.user = await this.authService.userProfile; } + transformArticleData(input: object): object { + input['categories'] = input['categories'].map(cat => { return { name: cat }}); + return input + } + createNewArticle() { - this.articleService.createNewArticle(this.article).then(article => - console.log(article) + alert('a'); + this.articleService.createNewArticle ( + (
this.transformArticleData(this.form.value)) + ).then(article => + console.warn(article) ); } addCategory() { - this.article.categories.push({name: ''}); + this.formCategories.push(new FormControl('')); } + } diff --git a/src/blogify/frontend/src/styles/forms.scss b/src/blogify/frontend/src/styles/forms.scss index 99268a8f..652be2e7 100644 --- a/src/blogify/frontend/src/styles/forms.scss +++ b/src/blogify/frontend/src/styles/forms.scss @@ -12,6 +12,10 @@ input, textarea { border-radius: $std-border-radius; border: 1px solid var(--border-color); + + &.ng-invalid.ng-touched { + border-color: var(--accent-negative); + } } input[type="checkbox"]:not(.togglebox) { @@ -112,6 +116,12 @@ button { font-size: 1.25em; font-weight: 600; + // Disabled + + &[disabled] { + opacity: .6; + } + // Color classes &.neutral { background: var(--accent-neutral); } From 4348d5eeaac19c7cab6c69d7ec72c8a01e670304 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Sat, 9 Nov 2019 20:35:44 -0500 Subject: [PATCH 36/75] validation: somewhat basic implementation --- src/blogify/backend/resources/Article.kt | 2 +- .../newarticle/new-article.component.ts | 51 ++++++++++++++----- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/blogify/backend/resources/Article.kt b/src/blogify/backend/resources/Article.kt index a1f76c63..583dc0df 100644 --- a/src/blogify/backend/resources/Article.kt +++ b/src/blogify/backend/resources/Article.kt @@ -29,7 +29,7 @@ import java.util.* property = "uuid" ) data class Article ( - val title: @check("\\w+") String, + val title: @check("^[^\\p{C}]+$") String, val createdAt: Long = Date().time, diff --git a/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts b/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts index f03d6ac9..4d55bfd4 100644 --- a/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts +++ b/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts @@ -4,8 +4,9 @@ import { ArticleService } from '../../services/article/article.service'; import { User } from '../../models/User'; import { StaticFile } from "../../models/Static"; import { AuthService } from '../../shared/auth/auth.service'; -import {AbstractControl, FormArray, FormControl, FormGroup, Validators} from '@angular/forms'; -import {DatePipe} from '@angular/common'; +import { AbstractControl, FormArray, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms'; +import { HttpClient } from '@angular/common/http'; +import { BehaviorSubject, Observable } from 'rxjs'; @Component({ selector: 'app-new-article', @@ -23,13 +24,45 @@ export class NewArticleComponent implements OnInit { createdBy: new User('', '', '', '', new StaticFile('-1')), createdAt: Date.now(), }; + user: User; + validations: object; + + constructor ( + private articleService: ArticleService, + private authService: AuthService, + private http: HttpClient + ) {} + + async ngOnInit() { + this.user = await this.authService.userProfile; + this.validations = await this.http.get('/api/articles/_validations').toPromise(); + console.warn(this.validations); + } + + private validateOnServer(fieldName: string): ValidatorFn { + const this_ = this; + return function (control: AbstractControl): ValidationErrors | null { + if (this_.validations === undefined) return null; // Validations are not ready; assume valid + const validationRegex = this_.validations[fieldName]; + if (validationRegex === undefined) return null; // No validation for the field; assume valid - datePipe = new DatePipe('en-US'); + const matchResult = ( control.value).match(validationRegex); // Match validation against value - formTitle = new FormControl('', [Validators.required]); - formSummary = new FormControl('', [Validators.required]); - formContent = new FormControl('', [Validators.required]); + if (matchResult !== null) { return null; } // Any matches; valid + else return { reason: `doesn't match regex '${validationRegex.source}'` }; // No matches; invalid + } + } + + formTitle = new FormControl('', + [Validators.required, this.validateOnServer('title')] + ); + formSummary = new FormControl('', + [Validators.required, this.validateOnServer('summary')] + ); + formContent = new FormControl('', + [Validators.required, this.validateOnServer('content')] + ); formCategories = new FormArray([new FormControl('')]); form = new FormGroup({ @@ -39,12 +72,6 @@ export class NewArticleComponent implements OnInit { 'categories': this.formCategories }); - constructor(private articleService: ArticleService, private authService: AuthService) {} - - async ngOnInit() { - this.user = await this.authService.userProfile; - } - transformArticleData(input: object): object { input['categories'] = input['categories'].map(cat => { return { name: cat }}); return input From dff578f96e71553531e76db41e07de77a6a41201 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Sat, 9 Nov 2019 21:03:57 -0500 Subject: [PATCH 37/75] SAA: fixed new-article button --- .../show-all-articles/show-all-articles.component.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts index 5899dc43..0437efe9 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts @@ -68,13 +68,10 @@ export class ShowAllArticlesComponent implements OnInit { } async navigateToNewArticle() { - if (! await this.authService.observeIsLoggedIn().toPromise()) { - const url = `/login?redirect=/article/new`; - console.log(url); - await this.router.navigateByUrl(url); - } else { - await this.router.navigateByUrl('/article/new'); - } + this.authService.observeIsLoggedIn().subscribe(it => { + if (it) this.router.navigateByUrl('/article/new'); + else this.router.navigateByUrl('/login?redirect=/article/new') + }); } } From 12570cfa371f112035cf6e12394751c409f72430 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Sat, 9 Nov 2019 21:04:01 -0500 Subject: [PATCH 38/75] validation: added result text (wip) --- .../newarticle/new-article.component.html | 8 ++++--- .../newarticle/new-article.component.scss | 23 ++++++++++++++++++- .../newarticle/new-article.component.ts | 12 ++++++---- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/blogify/frontend/src/app/components/newarticle/new-article.component.html b/src/blogify/frontend/src/app/components/newarticle/new-article.component.html index a87ac271..edbd745a 100644 --- a/src/blogify/frontend/src/app/components/newarticle/new-article.component.html +++ b/src/blogify/frontend/src/app/components/newarticle/new-article.component.html @@ -9,20 +9,22 @@

New Article

Title - + Summary - + Content - + + {{result.message}} + diff --git a/src/blogify/frontend/src/app/components/newarticle/new-article.component.scss b/src/blogify/frontend/src/app/components/newarticle/new-article.component.scss index b641c015..d93a8f08 100644 --- a/src/blogify/frontend/src/app/components/newarticle/new-article.component.scss +++ b/src/blogify/frontend/src/app/components/newarticle/new-article.component.scss @@ -26,7 +26,7 @@ margin-top: 1em; } - & span:first-child { + &:not(:last-of-type) span:first-child { width: 40%; font-size: 1.5em; @@ -43,10 +43,31 @@ } &#submit { + width: 60%; + margin-left: auto; + flex-direction: row; justify-content: flex-end; + align-items: center; * { font-size: 1.35em; } + #submit-result-container { + margin-right: auto; + text-align: center; + + &.none { + display: none; + } + + &.success { + color: var(--accent-positive); + } + + &.error { + color: var(--accent-negative); + } + } + button { flex-grow: 0; margin-left: .5em; diff --git a/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts b/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts index 4d55bfd4..47a71f29 100644 --- a/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts +++ b/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts @@ -6,7 +6,8 @@ import { StaticFile } from "../../models/Static"; import { AuthService } from '../../shared/auth/auth.service'; import { AbstractControl, FormArray, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms'; import { HttpClient } from '@angular/common/http'; -import { BehaviorSubject, Observable } from 'rxjs'; + +type Result = 'none' | 'success' | 'error'; @Component({ selector: 'app-new-article', @@ -28,6 +29,8 @@ export class NewArticleComponent implements OnInit { user: User; validations: object; + result: { status: Result, message: string } = { status: 'none', message: null }; + constructor ( private articleService: ArticleService, private authService: AuthService, @@ -78,11 +81,12 @@ export class NewArticleComponent implements OnInit { } createNewArticle() { - alert('a'); this.articleService.createNewArticle ( (
this.transformArticleData(this.form.value)) - ).then(article => - console.warn(article) + ).then(_ => + this.result = { status: 'success', message: 'Article created successfuly' } + ).catch(_ => + this.result = { status: 'error', message: 'Error while creating article' } ); } From d737a7834484ebd3ab1d5e24d84aa1f318d800b6 Mon Sep 17 00:00:00 2001 From: Hamza Date: Sun, 10 Nov 2019 14:39:59 +0500 Subject: [PATCH 39/75] epic searching (omega commit) --- docker-compose.yml | 18 ++-- .../backend/resources/search/Search.kt | 87 ++++++++++--------- .../backend/routes/articles/ArticleRoutes.kt | 40 ++++++--- .../backend/routes/handling/Handlers.kt | 6 +- .../services/articles/ArticleService.kt | 1 + 5 files changed, 85 insertions(+), 67 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2aea6e6f..79a2c757 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: - '5005:5005' depends_on: - db - - es + - ts volumes: - 'static-data:/var/static/' db: @@ -16,18 +16,16 @@ services: - '5432:5432' volumes: - 'db-data:/var/lib/postgresql/data' - es: - image: 'docker.elastic.co/elasticsearch/elasticsearch:7.4.0' + ts: + image: 'typesense/typesense:0.11.0' ports: - - '9200:9200' - - '9300:9300' - environment: - - 'discovery.type=single-node' - - 'node.name=es' + - '8108:8108' volumes: - - 'es-data:/usr/share/elasticsearch/data' + - 'ts-data:/data' + command: + --data-dir /data --api-key=Hu52dwsas2AdxdE volumes: db-data: static-data: - es-data: + ts-data: diff --git a/src/blogify/backend/resources/search/Search.kt b/src/blogify/backend/resources/search/Search.kt index 112139f2..4e2c748b 100644 --- a/src/blogify/backend/resources/search/Search.kt +++ b/src/blogify/backend/resources/search/Search.kt @@ -1,55 +1,56 @@ package blogify.backend.resources.search -import blogify.backend.resources.models.Resource +import blogify.backend.resources.Article +import blogify.backend.services.UserService +import java.util.* /** - * TODO complete doc - * * @author hamza1311 */ -data class Search ( - val _shards: Shards, - val hits: Hits, - val timed_out: Boolean, - val took: Int +data class Search( + val facet_counts: List, + val found: Int, + val hits: List, + val page: Int, + val search_time_ms: Int ) { - - /** - * TODO complete doc - */ - data class Shards ( - val failed: Int, - val skipped: Int, - val successful: Int, - val total: Int - ) - - /** - * TODO complete doc - */ - data class Hits ( - val hits: List>, - val max_score: Double, - val total: Total + data class Hit( + val document: Document, + val highlights: List ) - /** - * TODO complete doc - */ - data class Hit ( - val _id: String, - val _index: String, - val _score: Double, - val _source: S, - val _type: String - ) + data class Document( + val categories: List, + val content: String, + val createdAt: Double, + val createdBy: UUID, + val summary: String, + val title: String, + val id: UUID + ) { + suspend fun article(): Article = Article( + title = title, + content = content, + summary = summary, + createdBy = UserService.get(id = createdBy).get(), + categories = categories.map { Article.Category(it) }, + createdAt = createdAt.toLong(), + uuid = id + ) + } - /** - * TODO complete doc - */ - data class Total ( - val relation: String, - val value: Int + data class Highlight( + val `field`: String, + val snippet: String ) - } + +fun Article.asDocument(): Search.Document = Search.Document( + title = this.title, + content = this.content, + summary = this.summary, + createdBy = this.createdBy.uuid, + categories = this.categories.map { it.name }, + createdAt = this.createdAt.toDouble(), + id = this.uuid +) \ No newline at end of file diff --git a/src/blogify/backend/routes/articles/ArticleRoutes.kt b/src/blogify/backend/routes/articles/ArticleRoutes.kt index f00a54d3..d82c4d76 100644 --- a/src/blogify/backend/routes/articles/ArticleRoutes.kt +++ b/src/blogify/backend/routes/articles/ArticleRoutes.kt @@ -7,6 +7,7 @@ import blogify.backend.database.Users import blogify.backend.resources.Article import blogify.backend.resources.models.eqr import blogify.backend.resources.search.Search +import blogify.backend.resources.search.asDocument import blogify.backend.resources.slicing.sanitize import blogify.backend.resources.slicing.slice import blogify.backend.routes.handling.* @@ -17,16 +18,13 @@ import blogify.backend.services.models.Service import io.ktor.application.call import io.ktor.routing.* import io.ktor.client.HttpClient -import io.ktor.client.request.get -import io.ktor.client.request.post -import io.ktor.client.request.delete -import io.ktor.client.request.url import io.ktor.client.features.json.JsonFeature import io.ktor.content.TextContent import io.ktor.http.ContentType import io.ktor.response.respond import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import io.ktor.client.request.* fun Route.articles() { @@ -75,10 +73,10 @@ fun Route.articles() { doAfter = { id -> HttpClient().use { client -> client.delete { - url("http://es:9200/articles/_doc/$id") + url("http://ts:8108/collections/articles/documents/$id") + header("X-TYPESENSE-API-KEY", "Hu52dwsas2AdxdE") }.also { println(it) } } - // DELETE //_doc/<_id> } ) } @@ -87,7 +85,24 @@ fun Route.articles() { updateWithId ( update = ArticleService::update, fetch = ArticleService::get, - authPredicate = { user, article -> article.createdBy eqr user } + authPredicate = { user, article -> article.createdBy eqr user }, + doAfter = { replacement -> + HttpClient().use { client -> + client.delete { + url("http://ts:8108/collections/articles/documents/${replacement.uuid}") + header("X-TYPESENSE-API-KEY", "Hu52dwsas2AdxdE") + }.also { println(it) } + + val objectMapper = jacksonObjectMapper() + val jsonAsString = objectMapper.writeValueAsString(replacement.asDocument()) + println(jsonAsString) + client.post { + url("http://ts:8108/collections/articles/documents") + body = TextContent(jsonAsString, contentType = ContentType.Application.Json) + header("X-TYPESENSE-API-KEY", "Hu52dwsas2AdxdE") + }.also { println(it) } + } + } ) } @@ -98,11 +113,12 @@ fun Route.articles() { doAfter = { article -> HttpClient().use { client -> val objectMapper = jacksonObjectMapper() - val jsonAsString = objectMapper.writeValueAsString(article) + val jsonAsString = objectMapper.writeValueAsString(article.asDocument()) println(jsonAsString) client.post { - url("http://es:9200/articles/_create/${article.uuid}") + url("http://ts:8108/collections/articles/documents") body = TextContent(jsonAsString, contentType = ContentType.Application.Json) + header("X-TYPESENSE-API-KEY", "Hu52dwsas2AdxdE") }.also { println(it) } } } @@ -113,9 +129,9 @@ fun Route.articles() { val params = call.parameters val selectedPropertyNames = params["fields"]?.split(",")?.toSet() params["q"]?.let { query -> - HttpClient() { install(JsonFeature) }.use { client -> - val parsed = client.get>("http://es:9200/articles/_search?q=$query") - val hits = parsed.hits.hits.map { l -> l._source } + HttpClient { install(JsonFeature) }.use { client -> + val parsed = client.get("http://ts:8108/collections/articles/documents/search?q=$query&query_by=content,title") + val hits = parsed.hits.map { it.document.article() } try { selectedPropertyNames?.let { props -> diff --git a/src/blogify/backend/routes/handling/Handlers.kt b/src/blogify/backend/routes/handling/Handlers.kt index d8f220f7..cc1f019c 100644 --- a/src/blogify/backend/routes/handling/Handlers.kt +++ b/src/blogify/backend/routes/handling/Handlers.kt @@ -514,7 +514,8 @@ suspend fun CallPipeline.deleteWithId ( suspend inline fun CallPipeline.updateWithId ( noinline update: suspend (R) -> ResourceResult<*>, fetch: suspend (ApplicationCall, UUID) -> ResourceResult, - noinline authPredicate: suspend (User, R) -> Boolean = defaultPredicateLambda + noinline authPredicate: suspend (User, R) -> Boolean = defaultPredicateLambda, + noinline doAfter: suspend (R) -> Unit = {} ) { val replacement = call.receive() @@ -522,12 +523,13 @@ suspend inline fun CallPipeline.updateWithId ( val doUpdate: CallPipeLineFunction = { update(replacement).fold( success = { + doAfter(it as R) call.respond(HttpStatusCode.OK) }, failure = call::respondExceptionMessage ) } - + replacement.uuid.let { fetch.invoke(call, it) }.fold( success = { if (authPredicate != defaultPredicateLambda) { // Don't authenticate if the endpoint doesn't authenticate diff --git a/src/blogify/backend/services/articles/ArticleService.kt b/src/blogify/backend/services/articles/ArticleService.kt index edb5816b..935368e2 100644 --- a/src/blogify/backend/services/articles/ArticleService.kt +++ b/src/blogify/backend/services/articles/ArticleService.kt @@ -55,6 +55,7 @@ object ArticleService : Service
(Articles) { it[name] = cat.name } } + return@query res // So that we return the resource }.mapError { e -> Exception.Updating(e) } } From 2f75ceec60f664d4793e2ef8e3735d8f076f26c9 Mon Sep 17 00:00:00 2001 From: Hamza Date: Sun, 10 Nov 2019 14:56:43 +0500 Subject: [PATCH 40/75] Collection is created when the application is loaded --- src/blogify/backend/Application.kt | 48 +++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/blogify/backend/Application.kt b/src/blogify/backend/Application.kt index 295e366f..550e6a79 100644 --- a/src/blogify/backend/Application.kt +++ b/src/blogify/backend/Application.kt @@ -26,6 +26,11 @@ import io.ktor.response.respondRedirect import io.ktor.routing.get import io.ktor.application.Application import io.ktor.application.install +import io.ktor.client.HttpClient +import io.ktor.client.request.header +import io.ktor.client.request.post +import io.ktor.client.request.url +import io.ktor.content.TextContent import io.ktor.features.CachingHeaders import io.ktor.features.CallLogging import io.ktor.features.ContentNegotiation @@ -136,7 +141,48 @@ fun Application.mainModule(@Suppress("UNUSED_PARAMETER") testing: Boolean = fals Users, Comments, Uploadables - ) + ).also { + val json = """ + { + "name": "articles", + "fields": [ + { + "name": "title", + "type": "string" + }, + { + "name": "createdAt", + "type": "float" + }, + { + "name": "createdBy", + "type": "string" + }, + { + "name": "content", + "type": "string" + }, + { + "name": "summary", + "type": "string" + }, + { + "name": "categories", + "type": "string[]", + "facet": true + } + ], + "default_sorting_field": "createdAt" + } + """.trimIndent() + HttpClient().use { client -> + client.post { + url("http://ts:8108/collections") + body = TextContent(json, contentType = ContentType.Application.Json) + header("X-TYPESENSE-API-KEY", "Hu52dwsas2AdxdE") + }.also { println(it) } + } + } }} // Initialize routes From e51ac50bb6818fa62c79f8d74eccc3eefdbdf231 Mon Sep 17 00:00:00 2001 From: Hamza Date: Sun, 10 Nov 2019 15:02:55 +0500 Subject: [PATCH 41/75] `NumberFormatException` is no longer thrown if static resource id is empty string --- src/blogify/backend/routes/StaticRoutes.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/blogify/backend/routes/StaticRoutes.kt b/src/blogify/backend/routes/StaticRoutes.kt index ba89532f..4261c32b 100644 --- a/src/blogify/backend/routes/StaticRoutes.kt +++ b/src/blogify/backend/routes/StaticRoutes.kt @@ -42,10 +42,14 @@ fun Route.static() { pipeline("uploadableId") { (uploadableId) -> val actualId = uploadableId.takeWhile(Char::isDigit) // Allow for trailing extensions + if (actualId != "") { + val data = StaticFileHandler.readStaticResource(actualId.toLong()) - val data = StaticFileHandler.readStaticResource(actualId.toLong()) + call.respondBytes(data.bytes, data.contentType) + } else { + call.respond(HttpStatusCode.NoContent) + } - call.respondBytes(data.bytes, data.contentType) } } From 6a8644ab58aeb38b28f39674c35842c8952df786 Mon Sep 17 00:00:00 2001 From: Hamza Date: Sun, 10 Nov 2019 15:56:17 +0500 Subject: [PATCH 42/75] Comments count works --- src/blogify/backend/database/Tables.kt | 3 +++ src/blogify/backend/resources/Article.kt | 13 ++++++++----- .../src/app/components/home/home.component.ts | 2 +- .../components/newarticle/new-article.component.ts | 5 +++-- src/blogify/frontend/src/app/models/Article.ts | 1 + .../single-article-box.component.html | 2 +- 6 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/blogify/backend/database/Tables.kt b/src/blogify/backend/database/Tables.kt index 23204937..f1e291ef 100644 --- a/src/blogify/backend/database/Tables.kt +++ b/src/blogify/backend/database/Tables.kt @@ -48,6 +48,9 @@ object Articles : ResourceTable
() { createdBy = UserService.get(callContext, source[createdBy]).get(), content = source[content], summary = source[summary], + numberOfComments = transaction { + Comments.select { Comments.article eq source[uuid] }.count() + }, categories = transaction { Categories.select { Categories.article eq source[uuid] }.toList() }.map { Categories.convert(it) } diff --git a/src/blogify/backend/resources/Article.kt b/src/blogify/backend/resources/Article.kt index 583dc0df..c51b3332 100644 --- a/src/blogify/backend/resources/Article.kt +++ b/src/blogify/backend/resources/Article.kt @@ -16,11 +16,12 @@ import java.util.* /** * Represents an Article [Resource]. * - * @property title The title of the [Article]. - * @property createdAt The time of creation of the [Article], in `UNIX` timestamp format. - * @property createdBy The UUID of the [User] author of the article. - * @property content The [Content][Article.Content] of the article. Not included in the JSON serialization. - + * @property title The title of the [Article]. + * @property createdAt The time of creation of the [Article], in `UNIX` timestamp format. + * @property createdBy The UUID of the [User] author of the article. + * @property content The content of the article. + * @property summary The summary of the article. + * @property categories The [categories][Article.Category] of the article. */ @JsonIdentityInfo ( scope = Article::class, @@ -42,6 +43,8 @@ data class Article ( val categories: List, + val numberOfComments: Int = 0, + override val uuid: UUID = UUID.randomUUID() ) : Resource(uuid) { diff --git a/src/blogify/frontend/src/app/components/home/home.component.ts b/src/blogify/frontend/src/app/components/home/home.component.ts index 82ee6ff2..5c5d5501 100644 --- a/src/blogify/frontend/src/app/components/home/home.component.ts +++ b/src/blogify/frontend/src/app/components/home/home.component.ts @@ -16,7 +16,7 @@ export class HomeComponent implements OnInit { ngOnInit() { this.articleService.getAllArticles ( - ['title', 'summary', 'createdBy', 'categories', 'createdAt'] + ['title', 'summary', 'createdBy', 'categories', 'createdAt', 'numberOfComments'] ).then( articles => { this.articles = articles; console.log(articles); diff --git a/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts b/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts index 47a71f29..83857e5d 100644 --- a/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts +++ b/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts @@ -24,6 +24,7 @@ export class NewArticleComponent implements OnInit { summary: '', createdBy: new User('', '', '', '', new StaticFile('-1')), createdAt: Date.now(), + numberOfComments: 0, }; user: User; @@ -83,9 +84,9 @@ export class NewArticleComponent implements OnInit { createNewArticle() { this.articleService.createNewArticle ( (
this.transformArticleData(this.form.value)) - ).then(_ => + ).then(() => this.result = { status: 'success', message: 'Article created successfuly' } - ).catch(_ => + ).catch(() => this.result = { status: 'error', message: 'Error while creating article' } ); } diff --git a/src/blogify/frontend/src/app/models/Article.ts b/src/blogify/frontend/src/app/models/Article.ts index 084153a7..1c7d687f 100644 --- a/src/blogify/frontend/src/app/models/Article.ts +++ b/src/blogify/frontend/src/app/models/Article.ts @@ -10,6 +10,7 @@ export class Article { public createdBy: User | string, public createdAt: number, public categories: Category[], + public numberOfComments: number = 0, ) {} } diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.html b/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.html index 1d87c063..037ff2d0 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.html +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.html @@ -23,7 +23,7 @@

-

4

+

{{article.numberOfComments}}

diff --git a/src/blogify/frontend/src/app/services/article/article.service.ts b/src/blogify/frontend/src/app/services/article/article.service.ts index 8e984d6f..db8a40e4 100644 --- a/src/blogify/frontend/src/app/services/article/article.service.ts +++ b/src/blogify/frontend/src/app/services/article/article.service.ts @@ -46,7 +46,7 @@ export class ArticleService { return this.fetchUserObjects(articles); } - async createNewArticle(article: Article, userToken: string = this.authService.userToken): Promise { + async createNewArticle(article: Article, userToken: string = this.authService.userToken): Promise { const httpOptions = { headers: new HttpHeaders({ @@ -66,7 +66,7 @@ export class ArticleService { createdBy: await this.authService.userUUID, }; - return this.httpClient.post(`/api/articles/`, newArticle, httpOptions).toPromise(); + return this.httpClient.post(`/api/articles/`, newArticle, httpOptions).toPromise(); } updateArticle(article: Article, uuid: string = article.uuid, userToken: string = this.authService.userToken) { From 6774e72e195f47427bf3a78da3c37802e694e9ff Mon Sep 17 00:00:00 2001 From: Hamza Date: Sun, 10 Nov 2019 17:04:46 +0500 Subject: [PATCH 45/75] Changing this was a bad idea and remove unused emitted event listener --- .../src/app/components/comment/article-comments.component.html | 2 +- .../src/app/components/newarticle/new-article.component.ts | 3 +-- .../app/components/show-article/show-article.component.html | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/blogify/frontend/src/app/components/comment/article-comments.component.html b/src/blogify/frontend/src/app/components/comment/article-comments.component.html index ee42dc20..f992e09b 100644 --- a/src/blogify/frontend/src/app/components/comment/article-comments.component.html +++ b/src/blogify/frontend/src/app/components/comment/article-comments.component.html @@ -4,4 +4,4 @@ - + diff --git a/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts b/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts index 41cf7ee9..84397619 100644 --- a/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts +++ b/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts @@ -89,8 +89,7 @@ export class NewArticleComponent implements OnInit { ).then(async uuid => { this.result = { status: 'success', message: 'Article created successfully' }; await this.router.navigateByUrl(`/article/${uuid}`) - } - ).catch(() => + }).catch(() => this.result = { status: 'error', message: 'Error while creating article' } ); } diff --git a/src/blogify/frontend/src/app/components/show-article/show-article.component.html b/src/blogify/frontend/src/app/components/show-article/show-article.component.html index 1a52c90c..e8b7b494 100644 --- a/src/blogify/frontend/src/app/components/show-article/show-article.component.html +++ b/src/blogify/frontend/src/app/components/show-article/show-article.component.html @@ -60,7 +60,7 @@

Comments

- + From 770de34fd4f4dc0cc0c2d5d77232c0c73bce9ddc Mon Sep 17 00:00:00 2001 From: Hamza Date: Sun, 10 Nov 2019 17:20:02 +0500 Subject: [PATCH 46/75] Delete article redirects to home --- .../app/components/show-article/show-article.component.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/blogify/frontend/src/app/components/show-article/show-article.component.ts b/src/blogify/frontend/src/app/components/show-article/show-article.component.ts index 346adc2a..35415fa3 100644 --- a/src/blogify/frontend/src/app/components/show-article/show-article.component.ts +++ b/src/blogify/frontend/src/app/components/show-article/show-article.component.ts @@ -1,10 +1,9 @@ import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; +import {ActivatedRoute, Router} from '@angular/router'; import { Article } from '../../models/Article'; import { ArticleService } from '../../services/article/article.service'; import { Subscription } from 'rxjs'; import { AuthService } from '../../shared/auth/auth.service'; -import { CommentsService } from '../../services/comments/comments.service'; import { User } from '../../models/User'; @Component({ @@ -21,7 +20,7 @@ export class ShowArticleComponent implements OnInit { private activatedRoute: ActivatedRoute, private articleService: ArticleService, public authService: AuthService, - private commentsService: CommentsService + private router: Router ) {} showUpdateButton = false; @@ -47,6 +46,7 @@ export class ShowArticleComponent implements OnInit { deleteArticle() { this.articleService.deleteArticle(this.article.uuid).then(it => console.log(it)); + this.router.navigateByUrl("/home").then(() => {}) } } From f18d80b5000241d0c6bb204f17d3105c80c8de46 Mon Sep 17 00:00:00 2001 From: Hamza Date: Sun, 10 Nov 2019 17:50:00 +0500 Subject: [PATCH 47/75] Fix article update --- src/blogify/backend/services/articles/ArticleService.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/blogify/backend/services/articles/ArticleService.kt b/src/blogify/backend/services/articles/ArticleService.kt index 935368e2..a4655c7e 100644 --- a/src/blogify/backend/services/articles/ArticleService.kt +++ b/src/blogify/backend/services/articles/ArticleService.kt @@ -11,8 +11,6 @@ import com.github.kittinunf.result.coroutines.mapError import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.insert -import org.jetbrains.exposed.sql.transactions.TransactionManager -import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.update object ArticleService : Service
(Articles) { @@ -51,8 +49,9 @@ object ArticleService : Service
(Articles) { Articles.Categories.deleteWhere { Articles.Categories.article eq res.uuid } cats.forEach { cat -> - Articles.Categories.update { + Articles.Categories.insert { it[name] = cat.name + it[article] = res.uuid } } return@query res // So that we return the resource From ae1d385d6c74b2f8e33793460317c4b52a44a349 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Sun, 10 Nov 2019 13:40:52 -0500 Subject: [PATCH 48/75] new-article: slightly improved category UI --- .../newarticle/new-article.component.html | 22 ++++++++++++------- .../newarticle/new-article.component.scss | 20 ++++++++++++++--- .../newarticle/new-article.component.ts | 5 ++++- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/blogify/frontend/src/app/components/newarticle/new-article.component.html b/src/blogify/frontend/src/app/components/newarticle/new-article.component.html index edbd745a..233ec2f0 100644 --- a/src/blogify/frontend/src/app/components/newarticle/new-article.component.html +++ b/src/blogify/frontend/src/app/components/newarticle/new-article.component.html @@ -22,6 +22,20 @@

New Article

+ + Categories + + + + + + + + + + + + {{result.message}} @@ -29,13 +43,5 @@

New Article

- -
- -
-
- - - diff --git a/src/blogify/frontend/src/app/components/newarticle/new-article.component.scss b/src/blogify/frontend/src/app/components/newarticle/new-article.component.scss index d93a8f08..c67d9e82 100644 --- a/src/blogify/frontend/src/app/components/newarticle/new-article.component.scss +++ b/src/blogify/frontend/src/app/components/newarticle/new-article.component.scss @@ -42,13 +42,27 @@ flex-grow: 1; } + &#categories-row { + span:first-child { margin-right: auto; } + justify-content: flex-end; + + input { + margin: 0 .25em; + } + + #category-add { + margin-right: .7em; + height: 100%; + } + } + &#submit { width: 60%; margin-left: auto; flex-direction: row; justify-content: flex-end; - align-items: center; + * { font-size: 1.35em; } #submit-result-container { @@ -63,7 +77,7 @@ color: var(--accent-positive); } - &.error { + &.error { color: var(--accent-negative); } } @@ -77,7 +91,7 @@ display: flex; flex-direction: row; justify-content: space-between; - align-items: stretch; + align-items: center; @media (max-width: $query-desktop) { flex-direction: column; diff --git a/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts b/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts index 84397619..679903e1 100644 --- a/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts +++ b/src/blogify/frontend/src/app/components/newarticle/new-article.component.ts @@ -6,7 +6,8 @@ import { StaticFile } from "../../models/Static"; import { AuthService } from '../../shared/auth/auth.service'; import { AbstractControl, FormArray, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms'; import { HttpClient } from '@angular/common/http'; -import {Router} from "@angular/router"; +import { faPlus } from '@fortawesome/free-solid-svg-icons'; +import { Router } from "@angular/router"; type Result = 'none' | 'success' | 'error'; @@ -17,6 +18,8 @@ type Result = 'none' | 'success' | 'error'; }) export class NewArticleComponent implements OnInit { + faPlus = faPlus; + article: Article = { uuid: '', title: '', From 7cb1fcadc71b57efdcdac7711274bca2668dba1c Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Sun, 10 Nov 2019 13:40:57 -0500 Subject: [PATCH 49/75] SAA: added temp filtering component --- .../filtering-menu.component.html | 1 + .../filtering-menu.component.scss | 0 .../filtering-menu.component.spec.ts | 25 +++++++++++++++++++ .../filtering-menu.component.ts | 15 +++++++++++ .../show-all-articles.component.html | 2 ++ .../frontend/src/app/shared/shared.module.ts | 2 ++ 6 files changed, 45 insertions(+) create mode 100644 src/blogify/frontend/src/app/shared/components/show-all-articles/filtering-menu/filtering-menu.component.html create mode 100644 src/blogify/frontend/src/app/shared/components/show-all-articles/filtering-menu/filtering-menu.component.scss create mode 100644 src/blogify/frontend/src/app/shared/components/show-all-articles/filtering-menu/filtering-menu.component.spec.ts create mode 100644 src/blogify/frontend/src/app/shared/components/show-all-articles/filtering-menu/filtering-menu.component.ts diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/filtering-menu/filtering-menu.component.html b/src/blogify/frontend/src/app/shared/components/show-all-articles/filtering-menu/filtering-menu.component.html new file mode 100644 index 00000000..485dbe9d --- /dev/null +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/filtering-menu/filtering-menu.component.html @@ -0,0 +1 @@ +

filtering-menu works!

diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/filtering-menu/filtering-menu.component.scss b/src/blogify/frontend/src/app/shared/components/show-all-articles/filtering-menu/filtering-menu.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/filtering-menu/filtering-menu.component.spec.ts b/src/blogify/frontend/src/app/shared/components/show-all-articles/filtering-menu/filtering-menu.component.spec.ts new file mode 100644 index 00000000..a723ad3c --- /dev/null +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/filtering-menu/filtering-menu.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FilteringMenuComponent } from './filtering-menu.component'; + +describe('FilteringMenuComponent', () => { + let component: FilteringMenuComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FilteringMenuComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FilteringMenuComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/filtering-menu/filtering-menu.component.ts b/src/blogify/frontend/src/app/shared/components/show-all-articles/filtering-menu/filtering-menu.component.ts new file mode 100644 index 00000000..1d4785de --- /dev/null +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/filtering-menu/filtering-menu.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-filtering-menu', + templateUrl: './filtering-menu.component.html', + styleUrls: ['./filtering-menu.component.scss'] +}) +export class FilteringMenuComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html index 5b6d6017..a9e9eb4d 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html @@ -24,6 +24,8 @@

{{showingSearchResults ? 'Search results' : title}}

+ +
diff --git a/src/blogify/frontend/src/app/shared/shared.module.ts b/src/blogify/frontend/src/app/shared/shared.module.ts index 057f2778..81be437c 100644 --- a/src/blogify/frontend/src/app/shared/shared.module.ts +++ b/src/blogify/frontend/src/app/shared/shared.module.ts @@ -11,6 +11,7 @@ import { DarkThemeDirective } from './directives/dark-theme/dark-theme.directive import { CompactDirective } from './directives/compact/compact.directive'; import {FormsModule} from "@angular/forms"; import { SingleArticleBoxComponent } from './components/show-all-articles/single-article-box/single-article-box.component'; +import { FilteringMenuComponent } from './components/show-all-articles/filtering-menu/filtering-menu.component'; @NgModule({ declarations: [ @@ -22,6 +23,7 @@ import { SingleArticleBoxComponent } from './components/show-all-articles/single ShowAllArticlesComponent, UserDisplayComponent, SingleArticleBoxComponent, + FilteringMenuComponent, ], imports: [ CommonModule, From 4e41c35ccc3df2bce6749ffd23d826727bf145e7 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Sun, 10 Nov 2019 14:30:40 -0500 Subject: [PATCH 50/75] search: various fixes for new typesense backend --- .../backend/resources/search/Search.kt | 8 +++--- .../backend/routes/articles/ArticleRoutes.kt | 26 +++++++++--------- .../app/services/article/article.service.ts | 10 +++++-- .../show-all-articles.component.html | 6 ++++- .../show-all-articles.component.scss | 27 +++++++++++++++++++ .../show-all-articles.component.ts | 13 +++++++-- src/blogify/frontend/src/styles/forms.scss | 4 ++- 7 files changed, 71 insertions(+), 23 deletions(-) diff --git a/src/blogify/backend/resources/search/Search.kt b/src/blogify/backend/resources/search/Search.kt index 4e2c748b..ae6e8967 100644 --- a/src/blogify/backend/resources/search/Search.kt +++ b/src/blogify/backend/resources/search/Search.kt @@ -7,10 +7,10 @@ import java.util.* /** * @author hamza1311 */ -data class Search( - val facet_counts: List, - val found: Int, - val hits: List, +data class Search ( + val facet_counts: List?, // |\ + val found: Int?, // | Will not appear on no results + val hits: List?, // |/ val page: Int, val search_time_ms: Int ) { diff --git a/src/blogify/backend/routes/articles/ArticleRoutes.kt b/src/blogify/backend/routes/articles/ArticleRoutes.kt index d82c4d76..88c8c0ed 100644 --- a/src/blogify/backend/routes/articles/ArticleRoutes.kt +++ b/src/blogify/backend/routes/articles/ArticleRoutes.kt @@ -25,6 +25,7 @@ import io.ktor.response.respond import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import io.ktor.client.request.* +import io.ktor.http.HttpStatusCode fun Route.articles() { @@ -131,19 +132,18 @@ fun Route.articles() { params["q"]?.let { query -> HttpClient { install(JsonFeature) }.use { client -> val parsed = client.get("http://ts:8108/collections/articles/documents/search?q=$query&query_by=content,title") - val hits = parsed.hits.map { it.document.article() } - try { - selectedPropertyNames?.let { props -> - - call.respond(hits.map { it.slice(props) }) - - } ?: call.respond(hits.map { it.sanitize() }) - } catch (bruhMoment: Service.Exception) { - call.respondExceptionMessage(bruhMoment) - } - println("hits") - hits.forEach { println(it) } - call.respond(hits) + parsed.hits?.let { hits -> // Some hits + val hitResources = hits.map { it.document.article() } + try { + selectedPropertyNames?.let { props -> + + call.respond(hitResources.map { it.slice(props) }) + + } ?: call.respond(hitResources.map { it.sanitize() }) + } catch (bruhMoment: Service.Exception) { + call.respondExceptionMessage(bruhMoment) + } + } ?: call.respond(HttpStatusCode.NoContent) // No hits } } } diff --git a/src/blogify/frontend/src/app/services/article/article.service.ts b/src/blogify/frontend/src/app/services/article/article.service.ts index db8a40e4..c50f1b57 100644 --- a/src/blogify/frontend/src/app/services/article/article.service.ts +++ b/src/blogify/frontend/src/app/services/article/article.service.ts @@ -18,7 +18,7 @@ export class ArticleService { return this.fetchUserObjects(articles) } - private async fetchUserObjects(articles: Article[]) { + private async fetchUserObjects(articles: Article[]): Promise { const userUUIDs = articles .filter (it => typeof it.createdBy === 'string') .map (it => it.createdBy); @@ -104,7 +104,13 @@ export class ArticleService { const url = `/api/articles/search/?q=${query}&fields=${fields.join(',')}`; return this.httpClient.get(url) .toPromise() - .then(articles => this.fetchUserObjects(articles)); // Make sure user data is present + .then(hits => { + if (hits != null) { + return this.fetchUserObjects(hits); + } else { + return Promise.all([]); + } + }); // Make sure user data is present } } diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html index a9e9eb4d..b996630d 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html @@ -19,7 +19,7 @@

{{showingSearchResults ? 'Search results' : title}}

- + @@ -31,6 +31,10 @@

{{showingSearchResults ? 'Search results' : title}}

+
+ + No search results :( +
diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss index 40f692b6..58ef42ef 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss @@ -46,4 +46,31 @@ } } + #articles-main { + + } + + #search-results { + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: center; + + > * { width: 100%; } + + #results-empty { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + + margin-top: 5em; + + #empty-text { + font-size: 1.65em; + font-weight: 600; + } + } + } + } diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts index 0437efe9..27dea3e8 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts @@ -3,7 +3,7 @@ import { Article } from '../../../models/Article'; import { AuthService } from '../../auth/auth.service'; import { ActivatedRoute, Router, UrlSegment } from '@angular/router'; import { StaticContentService } from '../../../services/static/static-content.service'; -import { faArrowLeft, faPencilAlt, faSearch } from '@fortawesome/free-solid-svg-icons'; +import { faArrowLeft, faPencilAlt, faSearch, faTimes} from '@fortawesome/free-solid-svg-icons'; import { ArticleService } from '../../../services/article/article.service'; @Component({ @@ -17,10 +17,14 @@ export class ShowAllArticlesComponent implements OnInit { faPencil = faPencilAlt; faArrowLeft = faArrowLeft; + faCross = faTimes; + @Input() title = 'Articles'; @Input() articles: Article[]; @Input() allowCreate = true; + forceNoAllowCreate = false; + showingSearchResults = false; searchQuery: string; searchResults: Article[]; @@ -60,11 +64,16 @@ export class ShowAllArticlesComponent implements OnInit { ).then(it => { this.searchResults = it; this.showingSearchResults = true; - }) + this.forceNoAllowCreate = true; + }).catch((err: Error) => { + console.error(`[blogifySearch] Error while search: ${err.name}: ${err.message}`) + }); } async stopSearch() { this.showingSearchResults = false; + this.forceNoAllowCreate = false; + this.searchQuery = undefined; } async navigateToNewArticle() { diff --git a/src/blogify/frontend/src/styles/forms.scss b/src/blogify/frontend/src/styles/forms.scss index 652be2e7..b95d0883 100644 --- a/src/blogify/frontend/src/styles/forms.scss +++ b/src/blogify/frontend/src/styles/forms.scss @@ -8,11 +8,13 @@ input, button, textarea, select { input, textarea { color: var(--card-fg); - padding: .75em 1em; + padding: .65em .9em; border-radius: $std-border-radius; border: 1px solid var(--border-color); + font-size: 1.15em; + &.ng-invalid.ng-touched { border-color: var(--accent-negative); } From 3db9c6cfbba56f5b3c5ffefdbaf65b6d41fa6ceb Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Sun, 10 Nov 2019 16:51:41 -0500 Subject: [PATCH 51/75] commentCount: TODO give hamza lessons about entity relationships --- src/blogify/backend/database/Tables.kt | 3 -- src/blogify/backend/resources/Article.kt | 3 +- .../backend/routes/articles/ArticleRoutes.kt | 7 ++++ .../backend/routes/handling/Handlers.kt | 20 +++++++++++- .../backend/services/handling/Handlers.kt | 27 ++++++++++++++++ .../backend/services/models/Service.kt | 15 +++++++++ .../src/app/components/home/home.component.ts | 1 - .../app/services/article/article.service.ts | 32 +++++++++++++------ .../single-article-box.component.html | 1 - .../single-article-box.component.ts | 10 +++--- 10 files changed, 97 insertions(+), 22 deletions(-) diff --git a/src/blogify/backend/database/Tables.kt b/src/blogify/backend/database/Tables.kt index f1e291ef..23204937 100644 --- a/src/blogify/backend/database/Tables.kt +++ b/src/blogify/backend/database/Tables.kt @@ -48,9 +48,6 @@ object Articles : ResourceTable
() { createdBy = UserService.get(callContext, source[createdBy]).get(), content = source[content], summary = source[summary], - numberOfComments = transaction { - Comments.select { Comments.article eq source[uuid] }.count() - }, categories = transaction { Categories.select { Categories.article eq source[uuid] }.toList() }.map { Categories.convert(it) } diff --git a/src/blogify/backend/resources/Article.kt b/src/blogify/backend/resources/Article.kt index c51b3332..f98844bc 100644 --- a/src/blogify/backend/resources/Article.kt +++ b/src/blogify/backend/resources/Article.kt @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonIdentityReference import com.fasterxml.jackson.annotation.ObjectIdGenerators import blogify.backend.annotations.check +import blogify.backend.annotations.noslice import blogify.backend.database.Articles import blogify.backend.resources.models.Resource import blogify.backend.database.handling.query @@ -43,8 +44,6 @@ data class Article ( val categories: List, - val numberOfComments: Int = 0, - override val uuid: UUID = UUID.randomUUID() ) : Resource(uuid) { diff --git a/src/blogify/backend/routes/articles/ArticleRoutes.kt b/src/blogify/backend/routes/articles/ArticleRoutes.kt index 88c8c0ed..efe33e08 100644 --- a/src/blogify/backend/routes/articles/ArticleRoutes.kt +++ b/src/blogify/backend/routes/articles/ArticleRoutes.kt @@ -3,8 +3,10 @@ package blogify.backend.routes.articles import blogify.backend.database.Articles +import blogify.backend.database.Comments import blogify.backend.database.Users import blogify.backend.resources.Article +import blogify.backend.resources.Comment import blogify.backend.resources.models.eqr import blogify.backend.resources.search.Search import blogify.backend.resources.search.asDocument @@ -13,6 +15,7 @@ import blogify.backend.resources.slicing.slice import blogify.backend.routes.handling.* import blogify.backend.services.UserService import blogify.backend.services.articles.ArticleService +import blogify.backend.services.articles.CommentService import blogify.backend.services.models.Service import io.ktor.application.call @@ -39,6 +42,10 @@ fun Route.articles() { fetchWithId(ArticleService::get) } + get("/{uuid}/commentCount") { + countReferringToResource(ArticleService::get, CommentService::getReferring, Comments.article) + } + get("/forUser/{username}") { val params = call.parameters diff --git a/src/blogify/backend/routes/handling/Handlers.kt b/src/blogify/backend/routes/handling/Handlers.kt index e6e5ceb0..616560dc 100644 --- a/src/blogify/backend/routes/handling/Handlers.kt +++ b/src/blogify/backend/routes/handling/Handlers.kt @@ -44,6 +44,7 @@ import blogify.backend.services.models.ResourceResultSet import blogify.backend.services.models.Service import blogify.backend.annotations.BlogifyDsl import blogify.backend.annotations.type +import blogify.backend.database.ResourceTable import blogify.backend.resources.slicing.models.Mapped import blogify.backend.resources.slicing.okHandles import blogify.backend.resources.slicing.verify @@ -78,8 +79,9 @@ import com.andreapivetta.kolor.magenta import com.andreapivetta.kolor.yellow import com.github.kittinunf.result.coroutines.failure import com.github.kittinunf.result.coroutines.map -import org.jetbrains.exposed.sql.deleteWhere +import org.jetbrains.exposed.sql.Column +import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.select @@ -352,6 +354,22 @@ suspend inline fun CallPipeline.uploadToResource ( } +@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") +@BlogifyDsl +suspend fun CallPipeline.countReferringToResource ( + fetch: suspend (ApplicationCall, UUID) -> ResourceResult, + countReferences: suspend (Column, R) -> ResourceResult, + referenceField: Column +) = pipeline("uuid") { (uuid) -> + + // Find target resource + val targetResource = fetch(call, UUID.fromString(uuid)) + .getOrPipelineError(message = "couldn't fetch resource") // Handle result + + call.respond(countReferences(referenceField, targetResource).getOrPipelineError(message = "error while fetching comment count")) + +} + @BlogifyDsl suspend inline fun CallPipeline.deleteOnResource ( crossinline fetch: suspend (ApplicationCall, UUID) -> ResourceResult, diff --git a/src/blogify/backend/services/handling/Handlers.kt b/src/blogify/backend/services/handling/Handlers.kt index 8bc6a4ab..8641a6c6 100644 --- a/src/blogify/backend/services/handling/Handlers.kt +++ b/src/blogify/backend/services/handling/Handlers.kt @@ -12,6 +12,7 @@ import io.ktor.application.ApplicationCall import com.github.kittinunf.result.coroutines.map import com.github.kittinunf.result.coroutines.mapError +import org.jetbrains.exposed.sql.Column import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.selectAll @@ -24,6 +25,8 @@ import java.util.UUID * @param table the [ResourceTable] to query * * @return a [ResourceResultSet] that represents the success of the query, with a Database.Exception wrapped in if necessary. + * + * @author Benjozork */ suspend fun fetchAllFromTable(callContext: ApplicationCall, table: ResourceTable): ResourceResultSet { return query { @@ -43,6 +46,8 @@ suspend fun fetchAllFromTable(callContext: ApplicationCall, table * @param limit the number of [resources][Resource] to fetch * * @return a [ResourceResultSet] that represents the success of the query, with a Database.Exception wrapped in if necessary. + * + * @author Benjozork */ suspend fun fetchNumberFromTable(callContext: ApplicationCall, table: ResourceTable, limit: Int): ResourceResultSet { return query { @@ -62,6 +67,8 @@ suspend fun fetchNumberFromTable(callContext: ApplicationCall, ta * @param id the [UUID] of the resource to fetch * * @return a [ResourceResultSet] that represents the success of the query, with a Database.Exception wrapped in if necessary. + * + * @author Benjozork */ suspend fun fetchWithIdFromTable(callContext: ApplicationCall, table: ResourceTable, id: UUID): ResourceResult { return query { @@ -78,6 +85,8 @@ suspend fun fetchWithIdFromTable(callContext: ApplicationCall, ta * @param id the [UUID] of the resource to delete * * @return a [ResourceResult] that represents the success of the deletion, with a Database.Exception wrapped in if necessary. + * + * @author Benjozork */ suspend fun deleteWithIdInTable(table: ResourceTable, id: UUID): ResourceResult { return query { @@ -86,3 +95,21 @@ suspend fun deleteWithIdInTable(table: ResourceTable, id: UUID } .mapError { e -> Service.Exception.Deleting(e) } // Wrap a possible DBEx inside a Service exception } + +/** + * Returns the number of items in [referenceTable] that refer to [referenceValue] in their [referenceField] column. + * + * @param referenceTable the table to look for references in + * @param referenceField the column in which the reference is stored + * @param referenceValue the value to count occurrences for + * + * @return the number of instances of [referenceValue] in [referenceTable] + * + * @author Benjozork + */ +suspend fun countReferringInTable(referenceTable: ResourceTable, referenceField: Column, referenceValue: A): ResourceResult { + return query { + referenceTable.select { referenceField eq referenceValue }.count() + } + .mapError { e -> Service.Exception(e) } +} diff --git a/src/blogify/backend/services/models/Service.kt b/src/blogify/backend/services/models/Service.kt index bcd47576..f4e25f0b 100644 --- a/src/blogify/backend/services/models/Service.kt +++ b/src/blogify/backend/services/models/Service.kt @@ -4,6 +4,7 @@ import blogify.backend.database.ResourceTable import blogify.backend.resources.models.Resource import blogify.backend.resources.models.Resource.ObjectResolver.FakeApplicationCall import blogify.backend.services.caching.cachedOrElse +import blogify.backend.services.handling.countReferringInTable import blogify.backend.services.handling.deleteWithIdInTable import blogify.backend.services.handling.fetchNumberFromTable import blogify.backend.services.handling.fetchWithIdFromTable @@ -16,6 +17,7 @@ import com.github.kittinunf.result.coroutines.mapError import kotlinx.coroutines.runBlocking +import org.jetbrains.exposed.sql.Column import org.jetbrains.exposed.sql.Op import org.jetbrains.exposed.sql.SqlExpressionBuilder import org.jetbrains.exposed.sql.select @@ -87,6 +89,19 @@ abstract class Service(val table: ResourceTable) { }.mapError { Exception.Fetching(it) } } + /** + * Returns the number of items in [inTable] that refer to [withValue] in their [inField] column. + * + * @param inField the column in which the reference is stored + * @param withValue the [Resource] to count occurrences of + * + * @return the number of instances of [withValue] in [table] + * + * @author Benjozork + */ + suspend fun getReferring(inField: Column, withValue: T) + = countReferringInTable(table, inField, withValue.uuid) + abstract suspend fun add(res: R): ResourceResult abstract suspend fun update(res: R): SuspendableResult<*, Exception> diff --git a/src/blogify/frontend/src/app/components/home/home.component.ts b/src/blogify/frontend/src/app/components/home/home.component.ts index 5c5d5501..8f082071 100644 --- a/src/blogify/frontend/src/app/components/home/home.component.ts +++ b/src/blogify/frontend/src/app/components/home/home.component.ts @@ -19,7 +19,6 @@ export class HomeComponent implements OnInit { ['title', 'summary', 'createdBy', 'categories', 'createdAt', 'numberOfComments'] ).then( articles => { this.articles = articles; - console.log(articles); }); } diff --git a/src/blogify/frontend/src/app/services/article/article.service.ts b/src/blogify/frontend/src/app/services/article/article.service.ts index c50f1b57..5579aad3 100644 --- a/src/blogify/frontend/src/app/services/article/article.service.ts +++ b/src/blogify/frontend/src/app/services/article/article.service.ts @@ -9,14 +9,7 @@ import * as uuid from 'uuid/v4'; }) export class ArticleService { - constructor(private httpClient: HttpClient, private authService: AuthService) { - } - - async getAllArticles(fields: string[] = [], amount: number = 25): Promise { - const articlesObs = this.httpClient.get(`/api/articles/?fields=${fields.join(',')}&amount=${amount}`); - const articles = await articlesObs.toPromise(); - return this.fetchUserObjects(articles) - } + constructor(private httpClient: HttpClient, private authService: AuthService) {} private async fetchUserObjects(articles: Article[]): Promise { const userUUIDs = articles @@ -32,6 +25,23 @@ export class ArticleService { }); } + private async fetchCommentCount(articles: Article[]): Promise { + return Promise.all(articles.map(async a => { + a.numberOfComments = await this.httpClient.get('/api/articles/' + a.uuid + '/commentCount').toPromise(); + return a + })); + } + + private async prepareArticleData(articles: Article[]): Promise { + return this.fetchUserObjects(articles).then(articles2 => this.fetchCommentCount(articles2)) + } + + async getAllArticles(fields: string[] = [], amount: number = 25): Promise { + const articlesObs = this.httpClient.get(`/api/articles/?fields=${fields.join(',')}&amount=${amount}`); + const articles = await articlesObs.toPromise(); + return this.prepareArticleData(articles) + } + async getArticleByUUID(uuid: string, fields: string[] = []): Promise
{ const actualFieldsString: string = fields.length === 0 ? "" : `?fields=${fields.join(',')}`; @@ -46,6 +56,10 @@ export class ArticleService { return this.fetchUserObjects(articles); } + async getCommentCountForArticle(article: Article): Promise { + return this.httpClient.get('/api/articles/' + article.uuid + '/commentCount').toPromise() + } + async createNewArticle(article: Article, userToken: string = this.authService.userToken): Promise { const httpOptions = { @@ -106,7 +120,7 @@ export class ArticleService { .toPromise() .then(hits => { if (hits != null) { - return this.fetchUserObjects(hits); + return this.prepareArticleData(hits); } else { return Promise.all([]); } diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.html b/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.html index 037ff2d0..91ba4f19 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.html +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.html @@ -22,7 +22,6 @@

-

{{article.numberOfComments}}

diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.ts b/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.ts index 629019fe..96b917ab 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.ts +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/single-article-box/single-article-box.component.ts @@ -1,5 +1,6 @@ -import {Component, Input, OnInit} from '@angular/core'; -import {Article} from "../../../../models/Article"; +import { Component, Input, OnInit } from '@angular/core'; +import { Article } from "../../../../models/Article"; +import { ArticleService } from '../../../../services/article/article.service'; import { faCommentAlt } from '@fortawesome/free-solid-svg-icons'; @Component({ @@ -12,9 +13,8 @@ export class SingleArticleBoxComponent implements OnInit { @Input() article: Article; faCommentAlt = faCommentAlt; + constructor() {} - constructor() { } - - ngOnInit() { } + ngOnInit() {} } From 1de04db6676b7e197278d8faf174b567404d387b Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Sun, 10 Nov 2019 17:01:02 -0500 Subject: [PATCH 52/75] commentCount: improve docs for Service::getReferring --- src/blogify/backend/services/models/Service.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/blogify/backend/services/models/Service.kt b/src/blogify/backend/services/models/Service.kt index f4e25f0b..8e573a0d 100644 --- a/src/blogify/backend/services/models/Service.kt +++ b/src/blogify/backend/services/models/Service.kt @@ -90,9 +90,9 @@ abstract class Service(val table: ResourceTable) { } /** - * Returns the number of items in [inTable] that refer to [withValue] in their [inField] column. + * Returns the number of [R] that refer to [withValue] in [table]. * - * @param inField the column in which the reference is stored + * @param inField the column of [table] in which the reference is stored * @param withValue the [Resource] to count occurrences of * * @return the number of instances of [withValue] in [table] From 04de00d2a3da0a290c1888d6f959e8a18060876f Mon Sep 17 00:00:00 2001 From: Hamza Date: Mon, 11 Nov 2019 19:04:54 +0500 Subject: [PATCH 53/75] TODO: Give Ben lessons about javascript/typescript --- .../frontend/src/app/services/article/article.service.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/blogify/frontend/src/app/services/article/article.service.ts b/src/blogify/frontend/src/app/services/article/article.service.ts index 5579aad3..b683b29b 100644 --- a/src/blogify/frontend/src/app/services/article/article.service.ts +++ b/src/blogify/frontend/src/app/services/article/article.service.ts @@ -27,7 +27,7 @@ export class ArticleService { private async fetchCommentCount(articles: Article[]): Promise { return Promise.all(articles.map(async a => { - a.numberOfComments = await this.httpClient.get('/api/articles/' + a.uuid + '/commentCount').toPromise(); + a.numberOfComments = await this.httpClient.get(`/api/articles/${a.uuid}/commentCount`).toPromise(); return a })); } @@ -56,10 +56,6 @@ export class ArticleService { return this.fetchUserObjects(articles); } - async getCommentCountForArticle(article: Article): Promise { - return this.httpClient.get('/api/articles/' + article.uuid + '/commentCount').toPromise() - } - async createNewArticle(article: Article, userToken: string = this.authService.userToken): Promise { const httpOptions = { From ab121c16bdf682d0a609ead0e4302958bb45b250 Mon Sep 17 00:00:00 2001 From: Hamza Date: Mon, 11 Nov 2019 19:17:53 +0500 Subject: [PATCH 54/75] Temp fix for search bar not showing... again --- .../show-all-articles.component.html | 4 ++-- .../show-all-articles.component.scss | 12 ++++++------ .../show-all-articles/show-all-articles.component.ts | 6 ++++++ 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html index b996630d..97f9aa15 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html @@ -11,10 +11,10 @@

{{showingSearchResults ? 'Search results' : title}}

- + -
+ diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss index 58ef42ef..f0c34134 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss @@ -26,17 +26,17 @@ #header-search-pad { flex-grow: 1; - @media (min-width: 0) and (max-width: $search-icon-break) { - display: none; - } + //@media (min-width: 0) and (max-width: $search-icon-break) { + // display: none; + //} } #header-search-icon { margin-left: auto; - @media (min-width: $search-icon-break) { - display: none; - } + //@media (min-width: $search-icon-break) { + // display: none; + //} } #header-create-btn { diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts index 27dea3e8..c4f7d8ea 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts @@ -28,6 +28,7 @@ export class ShowAllArticlesComponent implements OnInit { showingSearchResults = false; searchQuery: string; searchResults: Article[]; + showSearchBar: boolean; constructor ( private authService: AuthService, @@ -74,6 +75,7 @@ export class ShowAllArticlesComponent implements OnInit { this.showingSearchResults = false; this.forceNoAllowCreate = false; this.searchQuery = undefined; + this.showSearchBar = false } async navigateToNewArticle() { @@ -83,4 +85,8 @@ export class ShowAllArticlesComponent implements OnInit { }); } + setShowSearchBar(val: boolean) { + this.showSearchBar = val + } + } From a3eb93c13a5544ac6f2e374d04d965d4ba5a76b6 Mon Sep 17 00:00:00 2001 From: Hamza Date: Mon, 11 Nov 2019 19:20:10 +0500 Subject: [PATCH 55/75] markdown in comment text --- .../comment/single-comment/single-comment.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blogify/frontend/src/app/components/comment/single-comment/single-comment.component.html b/src/blogify/frontend/src/app/components/comment/single-comment/single-comment.component.html index 9335cc78..27e220fe 100644 --- a/src/blogify/frontend/src/app/components/comment/single-comment/single-comment.component.html +++ b/src/blogify/frontend/src/app/components/comment/single-comment/single-comment.component.html @@ -5,7 +5,7 @@
- {{comment.content}} + {{comment.content}}
From 05349d0c16250041ac7c9103275ca12b6704007d Mon Sep 17 00:00:00 2001 From: Hamza Date: Mon, 11 Nov 2019 20:42:51 +0500 Subject: [PATCH 56/75] User search on backend --- .../backend/resources/search/Search.kt | 24 ++++++++++++----- .../backend/routes/articles/ArticleRoutes.kt | 3 +-- .../backend/routes/users/UserRoutes.kt | 27 +++++++++++++++++++ 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/blogify/backend/resources/search/Search.kt b/src/blogify/backend/resources/search/Search.kt index ae6e8967..c948b752 100644 --- a/src/blogify/backend/resources/search/Search.kt +++ b/src/blogify/backend/resources/search/Search.kt @@ -1,25 +1,27 @@ package blogify.backend.resources.search import blogify.backend.resources.Article +import blogify.backend.resources.User import blogify.backend.services.UserService +import io.ktor.application.ApplicationCall import java.util.* /** * @author hamza1311 */ -data class Search ( +data class Search ( val facet_counts: List?, // |\ val found: Int?, // | Will not appear on no results - val hits: List?, // |/ + val hits: List>?, // |/ val page: Int, val search_time_ms: Int ) { - data class Hit( - val document: Document, + data class Hit( + val document: D, val highlights: List ) - data class Document( + data class ArticleDocument( val categories: List, val content: String, val createdAt: Double, @@ -39,13 +41,23 @@ data class Search ( ) } + data class UserDocument( + val username: String, + val name: String, + val email: String, + val dsf_jank: Int, + val id: UUID + ) { + suspend fun user(callContext: ApplicationCall): User = UserService.get(callContext, id).get() + } + data class Highlight( val `field`: String, val snippet: String ) } -fun Article.asDocument(): Search.Document = Search.Document( +fun Article.asDocument(): Search.ArticleDocument = Search.ArticleDocument( title = this.title, content = this.content, summary = this.summary, diff --git a/src/blogify/backend/routes/articles/ArticleRoutes.kt b/src/blogify/backend/routes/articles/ArticleRoutes.kt index efe33e08..6ae737aa 100644 --- a/src/blogify/backend/routes/articles/ArticleRoutes.kt +++ b/src/blogify/backend/routes/articles/ArticleRoutes.kt @@ -6,7 +6,6 @@ import blogify.backend.database.Articles import blogify.backend.database.Comments import blogify.backend.database.Users import blogify.backend.resources.Article -import blogify.backend.resources.Comment import blogify.backend.resources.models.eqr import blogify.backend.resources.search.Search import blogify.backend.resources.search.asDocument @@ -138,7 +137,7 @@ fun Route.articles() { val selectedPropertyNames = params["fields"]?.split(",")?.toSet() params["q"]?.let { query -> HttpClient { install(JsonFeature) }.use { client -> - val parsed = client.get("http://ts:8108/collections/articles/documents/search?q=$query&query_by=content,title") + val parsed = client.get>("http://ts:8108/collections/articles/documents/search?q=$query&query_by=content,title") parsed.hits?.let { hits -> // Some hits val hitResources = hits.map { it.document.article() } try { diff --git a/src/blogify/backend/routes/users/UserRoutes.kt b/src/blogify/backend/routes/users/UserRoutes.kt index be060718..899123d5 100644 --- a/src/blogify/backend/routes/users/UserRoutes.kt +++ b/src/blogify/backend/routes/users/UserRoutes.kt @@ -3,6 +3,7 @@ package blogify.backend.routes.users import blogify.backend.database.Users import blogify.backend.resources.User import blogify.backend.resources.models.eqr +import blogify.backend.resources.search.Search import blogify.backend.resources.slicing.sanitize import blogify.backend.resources.slicing.slice import blogify.backend.routes.handling.* @@ -10,6 +11,10 @@ import blogify.backend.services.UserService import blogify.backend.services.models.Service import io.ktor.application.call +import io.ktor.client.HttpClient +import io.ktor.client.features.json.JsonFeature +import io.ktor.client.request.get +import io.ktor.http.HttpStatusCode import io.ktor.response.respond import io.ktor.routing.* @@ -75,6 +80,28 @@ fun Route.users() { ) } + get("/search") { + val params = call.parameters + val selectedPropertyNames = params["fields"]?.split(",")?.toSet() + params["q"]?.let { query -> + HttpClient { install(JsonFeature) }.use { client -> + val parsed = client.get>("http://ts:8108/collections/users/documents/search?q=$query&query_by=username,name,email") + parsed.hits?.let { hits -> // Some hits + val hitResources = hits.map { it.document.user(call) } + try { + selectedPropertyNames?.let { props -> + + call.respond(hitResources.map { it.slice(props) }) + + } ?: call.respond(hitResources.map { it.sanitize() }) + } catch (bruhMoment: Service.Exception) { + call.respondExceptionMessage(bruhMoment) + } + } ?: call.respond(HttpStatusCode.NoContent) // No hits + } + } + } + } } From 9a02021c3d5e925f566667a368d517466261d66d Mon Sep 17 00:00:00 2001 From: Hamza Date: Mon, 11 Nov 2019 20:49:33 +0500 Subject: [PATCH 57/75] Add user to index on signup --- .../backend/resources/search/Search.kt | 9 +++++++++ src/blogify/backend/routes/AuthRoutes.kt | 20 ++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/blogify/backend/resources/search/Search.kt b/src/blogify/backend/resources/search/Search.kt index c948b752..346f4f3b 100644 --- a/src/blogify/backend/resources/search/Search.kt +++ b/src/blogify/backend/resources/search/Search.kt @@ -65,4 +65,13 @@ fun Article.asDocument(): Search.ArticleDocument = Search.ArticleDocument( categories = this.categories.map { it.name }, createdAt = this.createdAt.toDouble(), id = this.uuid +) + + +fun User.asDocument(): Search.UserDocument = Search.UserDocument( + username = this.username, + name = this.name, + email = this.email, + dsf_jank = 0, + id = this.uuid ) \ No newline at end of file diff --git a/src/blogify/backend/routes/AuthRoutes.kt b/src/blogify/backend/routes/AuthRoutes.kt index 854df3e1..3824243f 100644 --- a/src/blogify/backend/routes/AuthRoutes.kt +++ b/src/blogify/backend/routes/AuthRoutes.kt @@ -6,13 +6,20 @@ import blogify.backend.auth.jwt.generateJWT import blogify.backend.auth.jwt.validateJwt import blogify.backend.database.Users import blogify.backend.resources.User +import blogify.backend.resources.search.asDocument import blogify.backend.resources.static.models.StaticResourceHandle import blogify.backend.routes.handling.respondExceptionMessage import blogify.backend.services.UserService import blogify.backend.services.models.Service import blogify.backend.util.* +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import io.ktor.application.call +import io.ktor.client.HttpClient +import io.ktor.client.request.header +import io.ktor.client.request.post +import io.ktor.client.request.url +import io.ktor.content.TextContent import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode import io.ktor.request.receive @@ -63,7 +70,18 @@ data class RegisterCredentials ( ) UserService.add(created).fold( - success = {}, + success = { user -> + HttpClient().use { client -> + val objectMapper = jacksonObjectMapper() + val jsonAsString = objectMapper.writeValueAsString(user.asDocument()) + println(jsonAsString) + client.post { + url("http://ts:8108/collections/users/documents") + body = TextContent(jsonAsString, contentType = ContentType.Application.Json) + header("X-TYPESENSE-API-KEY", "Hu52dwsas2AdxdE") + }.also { println(it) } + } + }, failure = { error("$created: signup couldn't create user\nError:$it") } From 1879312abb1f0722dd55f2719374550d4baef193 Mon Sep 17 00:00:00 2001 From: Hamza Date: Mon, 11 Nov 2019 20:55:35 +0500 Subject: [PATCH 58/75] create user index on application boot --- src/blogify/backend/Application.kt | 36 +++++++++++++++++-- .../backend/util/FileCollectionTypes.kt | 5 --- src/blogify/backend/util/Misc.kt | 5 +-- 3 files changed, 34 insertions(+), 12 deletions(-) delete mode 100644 src/blogify/backend/util/FileCollectionTypes.kt diff --git a/src/blogify/backend/Application.kt b/src/blogify/backend/Application.kt index 550e6a79..e123c615 100644 --- a/src/blogify/backend/Application.kt +++ b/src/blogify/backend/Application.kt @@ -18,6 +18,7 @@ import blogify.backend.database.handling.query import blogify.backend.resources.models.Resource import blogify.backend.routes.static import blogify.backend.util.SinglePageApplication +import blogify.backend.util.TYPESENSE_API_KEY import io.ktor.application.call import io.ktor.features.Compression @@ -142,7 +143,7 @@ fun Application.mainModule(@Suppress("UNUSED_PARAMETER") testing: Boolean = fals Comments, Uploadables ).also { - val json = """ + val articleJson = """ { "name": "articles", "fields": [ @@ -175,13 +176,42 @@ fun Application.mainModule(@Suppress("UNUSED_PARAMETER") testing: Boolean = fals "default_sorting_field": "createdAt" } """.trimIndent() + val userJson = """{ + "name": "users", + "fields": [ + { + "name": "username", + "type": "string" + }, + { + "name": "name", + "type": "string" + }, + { + "name": "email", + "type": "string" + }, + { + "name": "dsf_jank", + "type": "int32" + } + ], + "default_sorting_field": "dsf_jank" + }""".trimIndent() + HttpClient().use { client -> client.post { url("http://ts:8108/collections") - body = TextContent(json, contentType = ContentType.Application.Json) - header("X-TYPESENSE-API-KEY", "Hu52dwsas2AdxdE") + body = TextContent(articleJson, contentType = ContentType.Application.Json) + header("X-TYPESENSE-API-KEY", TYPESENSE_API_KEY) + }.also { println(it) } + client.post { + url("http://ts:8108/collections") + body = TextContent(userJson, contentType = ContentType.Application.Json) + header("X-TYPESENSE-API-KEY", TYPESENSE_API_KEY) }.also { println(it) } } + } }} diff --git a/src/blogify/backend/util/FileCollectionTypes.kt b/src/blogify/backend/util/FileCollectionTypes.kt deleted file mode 100644 index bca0df9c..00000000 --- a/src/blogify/backend/util/FileCollectionTypes.kt +++ /dev/null @@ -1,5 +0,0 @@ -package blogify.backend.util - -enum class FileCollectionTypes { - USER_PROFILE_PICTURES -} diff --git a/src/blogify/backend/util/Misc.kt b/src/blogify/backend/util/Misc.kt index ac6c5c64..3ff9c76e 100644 --- a/src/blogify/backend/util/Misc.kt +++ b/src/blogify/backend/util/Misc.kt @@ -14,7 +14,4 @@ fun T.letCatchingOrNull(block: (T) -> R): R? { infix fun ContentType.matches(other: ContentType) = this.match(other) -fun Byte.hex(): String { - val raw = this.toInt().toString(16).toUpperCase() - return if (raw.length == 1) "0$raw" else raw -} +const val TYPESENSE_API_KEY = "Hu52dwsas2AdxdE" \ No newline at end of file From c8996a859e9ed4b7a748f247e2f5f6f626a1c05a Mon Sep 17 00:00:00 2001 From: Hamza Date: Mon, 11 Nov 2019 20:56:49 +0500 Subject: [PATCH 59/75] constant > string literal --- src/blogify/backend/routes/AuthRoutes.kt | 2 +- src/blogify/backend/routes/articles/ArticleRoutes.kt | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/blogify/backend/routes/AuthRoutes.kt b/src/blogify/backend/routes/AuthRoutes.kt index 3824243f..12217a7e 100644 --- a/src/blogify/backend/routes/AuthRoutes.kt +++ b/src/blogify/backend/routes/AuthRoutes.kt @@ -78,7 +78,7 @@ data class RegisterCredentials ( client.post { url("http://ts:8108/collections/users/documents") body = TextContent(jsonAsString, contentType = ContentType.Application.Json) - header("X-TYPESENSE-API-KEY", "Hu52dwsas2AdxdE") + header("X-TYPESENSE-API-KEY", TYPESENSE_API_KEY) }.also { println(it) } } }, diff --git a/src/blogify/backend/routes/articles/ArticleRoutes.kt b/src/blogify/backend/routes/articles/ArticleRoutes.kt index 6ae737aa..c2363b4c 100644 --- a/src/blogify/backend/routes/articles/ArticleRoutes.kt +++ b/src/blogify/backend/routes/articles/ArticleRoutes.kt @@ -16,6 +16,7 @@ import blogify.backend.services.UserService import blogify.backend.services.articles.ArticleService import blogify.backend.services.articles.CommentService import blogify.backend.services.models.Service +import blogify.backend.util.TYPESENSE_API_KEY import io.ktor.application.call import io.ktor.routing.* @@ -81,7 +82,7 @@ fun Route.articles() { HttpClient().use { client -> client.delete { url("http://ts:8108/collections/articles/documents/$id") - header("X-TYPESENSE-API-KEY", "Hu52dwsas2AdxdE") + header("X-TYPESENSE-API-KEY", TYPESENSE_API_KEY) }.also { println(it) } } } @@ -97,7 +98,7 @@ fun Route.articles() { HttpClient().use { client -> client.delete { url("http://ts:8108/collections/articles/documents/${replacement.uuid}") - header("X-TYPESENSE-API-KEY", "Hu52dwsas2AdxdE") + header("X-TYPESENSE-API-KEY", TYPESENSE_API_KEY) }.also { println(it) } val objectMapper = jacksonObjectMapper() @@ -106,7 +107,7 @@ fun Route.articles() { client.post { url("http://ts:8108/collections/articles/documents") body = TextContent(jsonAsString, contentType = ContentType.Application.Json) - header("X-TYPESENSE-API-KEY", "Hu52dwsas2AdxdE") + header("X-TYPESENSE-API-KEY", TYPESENSE_API_KEY) }.also { println(it) } } } @@ -125,7 +126,7 @@ fun Route.articles() { client.post { url("http://ts:8108/collections/articles/documents") body = TextContent(jsonAsString, contentType = ContentType.Application.Json) - header("X-TYPESENSE-API-KEY", "Hu52dwsas2AdxdE") + header("X-TYPESENSE-API-KEY", TYPESENSE_API_KEY) }.also { println(it) } } } From 67812480c599b445cbc09edbc87bb82c0fc96656 Mon Sep 17 00:00:00 2001 From: Hamza Date: Mon, 11 Nov 2019 21:09:39 +0500 Subject: [PATCH 60/75] Kdocs --- .../backend/resources/search/Search.kt | 33 ++++++++++++++++++- src/blogify/backend/routes/AuthRoutes.kt | 2 +- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/blogify/backend/resources/search/Search.kt b/src/blogify/backend/resources/search/Search.kt index 346f4f3b..4d8bfc0e 100644 --- a/src/blogify/backend/resources/search/Search.kt +++ b/src/blogify/backend/resources/search/Search.kt @@ -7,6 +7,8 @@ import io.ktor.application.ApplicationCall import java.util.* /** + * Models for deserializing json returned by typesense + * * @author hamza1311 */ data class Search ( @@ -21,6 +23,9 @@ data class Search ( val highlights: List ) + /** + * Model representing an [article][Article] hit returned by typesense + */ data class ArticleDocument( val categories: List, val content: String, @@ -30,6 +35,14 @@ data class Search ( val title: String, val id: UUID ) { + + /** + * Convert [ArticleDocument] to [Article]. + * It constructs the [article][Article] object using the properties of the given [document][ArticleDocument] + * It does **NOT** makes a database call + * + * @return The article object created by properties of the given [document][ArticleDocument] + */ suspend fun article(): Article = Article( title = title, content = content, @@ -41,6 +54,11 @@ data class Search ( ) } + /** + * Model representing an [user][User] hit returned by typesense + * + * @param dsf_jank This is a workaround for `default_sorting_field` parameter in typesense, which is a required parameter whose value can only be a `float` or `int32`. Its value is always `0` in our case + */ data class UserDocument( val username: String, val name: String, @@ -48,6 +66,14 @@ data class Search ( val dsf_jank: Int, val id: UUID ) { + + /** + * Convert [UserDocument] to [User]. + * It constructs the [user][User] object by fetcting user with uuid of [id] from [users][blogify.backend.database.Users] table + * This is a database call + * + * @return The user object with uuid of [id] + */ suspend fun user(callContext: ApplicationCall): User = UserService.get(callContext, id).get() } @@ -57,6 +83,9 @@ data class Search ( ) } +/** + * Constructs [Search.ArticleDocument] from [Article] + */ fun Article.asDocument(): Search.ArticleDocument = Search.ArticleDocument( title = this.title, content = this.content, @@ -67,7 +96,9 @@ fun Article.asDocument(): Search.ArticleDocument = Search.ArticleDocument( id = this.uuid ) - +/** + * Constructs [Search.UserDocument] from [User] + */ fun User.asDocument(): Search.UserDocument = Search.UserDocument( username = this.username, name = this.name, diff --git a/src/blogify/backend/routes/AuthRoutes.kt b/src/blogify/backend/routes/AuthRoutes.kt index 12217a7e..e0168914 100644 --- a/src/blogify/backend/routes/AuthRoutes.kt +++ b/src/blogify/backend/routes/AuthRoutes.kt @@ -129,7 +129,7 @@ fun Route.auth() { call.parameters["token"]?.let { token -> validateJwt(call, token).fold( - success = { call.respond( object { val uuid = it.uuid }) }, + success = { call.respond( object { @Suppress("unused") val uuid = it.uuid }) }, failure = { call.respondExceptionMessage(Service.Exception(BException(it))) } ) From c5f8a26ab09226eee62f81c6a533765359e4cf05 Mon Sep 17 00:00:00 2001 From: Hamza Date: Mon, 11 Nov 2019 21:13:20 +0500 Subject: [PATCH 61/75] User search function in frontend (untested) --- src/blogify/backend/resources/search/Search.kt | 2 +- .../frontend/src/app/shared/auth/auth.service.ts | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/blogify/backend/resources/search/Search.kt b/src/blogify/backend/resources/search/Search.kt index 4d8bfc0e..71fe01d9 100644 --- a/src/blogify/backend/resources/search/Search.kt +++ b/src/blogify/backend/resources/search/Search.kt @@ -14,7 +14,7 @@ import java.util.* data class Search ( val facet_counts: List?, // |\ val found: Int?, // | Will not appear on no results - val hits: List>?, // |/ + val hits: List>?, // |/ val page: Int, val search_time_ms: Int ) { diff --git a/src/blogify/frontend/src/app/shared/auth/auth.service.ts b/src/blogify/frontend/src/app/shared/auth/auth.service.ts index 0cee8260..1ef58b94 100644 --- a/src/blogify/frontend/src/app/shared/auth/auth.service.ts +++ b/src/blogify/frontend/src/app/shared/auth/auth.service.ts @@ -4,7 +4,6 @@ import { LoginCredentials, RegisterCredentials, User } from 'src/app/models/User import { BehaviorSubject, Observable } from 'rxjs'; import { StaticFile } from '../../models/Static'; import { StaticContentService } from '../../services/static/static-content.service'; -import {Router} from '@angular/router'; @Injectable({ providedIn: 'root' @@ -20,7 +19,6 @@ export class AuthService { constructor ( private httpClient: HttpClient, private staticContentService: StaticContentService, - private router: Router ) { this.attemptRestoreLogin() } @@ -31,9 +29,9 @@ export class AuthService { console.info('[blogifyAuth] No stored token'); } else { this.login(token).then ( - (res) => { + () => { console.info('[blogifyAuth] Logged in with stored token') - }, (err) => { + }, () => { console.error('[blogifyAuth] Error while attempting stored token, not logging in and clearing token.'); localStorage.removeItem('userToken'); }); @@ -94,6 +92,7 @@ export class AuthService { return this.httpClient.get(`/api/users/${uuid}`).toPromise() } + // noinspection JSMethodCanBeStatic get userToken(): string | null { return localStorage.getItem('userToken'); } @@ -131,6 +130,11 @@ export class AuthService { return this.staticContentService.uploadFile(file, userToken, `/api/users/profilePicture/${userUUID}/?target=profilePicture`) } + search(query: string, fields: string[]) { + const url = `/api/articles/search/?q=${query}&fields=${fields.join(',')}`; + return this.httpClient.get(url).toPromise() + } + } interface UserToken { From 2b468915fd777da6374aa4d76ca478c028961e65 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Mon, 11 Nov 2019 14:14:22 -0500 Subject: [PATCH 62/75] formatting --- src/blogify/backend/routes/articles/ArticleRoutes.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/blogify/backend/routes/articles/ArticleRoutes.kt b/src/blogify/backend/routes/articles/ArticleRoutes.kt index c2363b4c..52603cec 100644 --- a/src/blogify/backend/routes/articles/ArticleRoutes.kt +++ b/src/blogify/backend/routes/articles/ArticleRoutes.kt @@ -25,11 +25,11 @@ import io.ktor.client.features.json.JsonFeature import io.ktor.content.TextContent import io.ktor.http.ContentType import io.ktor.response.respond - -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import io.ktor.client.request.* import io.ktor.http.HttpStatusCode +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper + fun Route.articles() { route("/articles") { From eccfb2ae444ae0f83ea33d6e2e312d6d07031476 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Mon, 11 Nov 2019 14:14:26 -0500 Subject: [PATCH 63/75] SAA: fixed mobile search bar for good --- .../show-all-articles.component.html | 22 +++++++++++++++---- .../show-all-articles.component.scss | 16 ++++++++------ .../show-all-articles.component.ts | 6 ++--- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html index 97f9aa15..c58b0613 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html @@ -7,14 +7,28 @@ -

{{showingSearchResults ? 'Search results' : title}}

+

{{showingSearchResults ? 'Search results' : title}}

- + - + + + + + - + diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss index f0c34134..0a999bf6 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.scss @@ -24,19 +24,21 @@ margin-right: 1em; } - #header-search-pad { + #header-search-pad, #header-mobile-search-pad { flex-grow: 1; - //@media (min-width: 0) and (max-width: $search-icon-break) { - // display: none; - //} + &#header-search-pad { + @media (min-width: 0) and (max-width: $search-icon-break) { + display: none; + } + } } #header-search-icon { margin-left: auto; - //@media (min-width: $search-icon-break) { - // display: none; - //} + @media (min-width: $search-icon-break) { + display: none; + } } #header-create-btn { diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts index c4f7d8ea..df7d24a0 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts @@ -28,7 +28,7 @@ export class ShowAllArticlesComponent implements OnInit { showingSearchResults = false; searchQuery: string; searchResults: Article[]; - showSearchBar: boolean; + showingMobileSearchBar: boolean; constructor ( private authService: AuthService, @@ -75,7 +75,7 @@ export class ShowAllArticlesComponent implements OnInit { this.showingSearchResults = false; this.forceNoAllowCreate = false; this.searchQuery = undefined; - this.showSearchBar = false + this.showingMobileSearchBar = false } async navigateToNewArticle() { @@ -86,7 +86,7 @@ export class ShowAllArticlesComponent implements OnInit { } setShowSearchBar(val: boolean) { - this.showSearchBar = val + this.showingMobileSearchBar = val } } From e7f591ad74cdff6e0dc228597664066c3ca88c37 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Mon, 11 Nov 2019 14:15:11 -0500 Subject: [PATCH 64/75] SAA: naming fix --- .../show-all-articles/show-all-articles.component.html | 2 +- .../components/show-all-articles/show-all-articles.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html index c58b0613..03a4893e 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html @@ -46,7 +46,7 @@

{{showingSearchResults ? '
- + No search results :(
diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts index df7d24a0..53db9fd5 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.ts @@ -17,7 +17,7 @@ export class ShowAllArticlesComponent implements OnInit { faPencil = faPencilAlt; faArrowLeft = faArrowLeft; - faCross = faTimes; + faTimes = faTimes; @Input() title = 'Articles'; @Input() articles: Article[]; From b72f1384d87a8fa8edf48e8793d366a80d4321fc Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Mon, 11 Nov 2019 14:23:15 -0500 Subject: [PATCH 65/75] table-defs: formatting --- src/blogify/backend/database/Tables.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/blogify/backend/database/Tables.kt b/src/blogify/backend/database/Tables.kt index 23204937..c784d185 100644 --- a/src/blogify/backend/database/Tables.kt +++ b/src/blogify/backend/database/Tables.kt @@ -12,18 +12,18 @@ import blogify.backend.services.UserService import blogify.backend.services.articles.CommentService import blogify.backend.services.models.Service -import com.github.kittinunf.result.coroutines.SuspendableResult - import io.ktor.application.ApplicationCall import io.ktor.http.ContentType -import org.jetbrains.exposed.sql.Column +import org.jetbrains.exposed.sql.Column import org.jetbrains.exposed.sql.ReferenceOption.* import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.Table import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction +import com.github.kittinunf.result.coroutines.SuspendableResult + abstract class ResourceTable : Table() { abstract suspend fun convert(callContext: ApplicationCall, source: ResultRow): SuspendableResult @@ -34,7 +34,7 @@ abstract class ResourceTable : Table() { object Articles : ResourceTable
() { - val title: Column = varchar ("title", 512) + val title = varchar ("title", 512) val createdAt = long ("created_at") val createdBy = uuid ("created_by").references(Users.uuid, onDelete = SET_NULL) val content = text ("content") From 3caa1f3525d518591bae259ba1d4a9c18325a24c Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Mon, 11 Nov 2019 14:24:07 -0500 Subject: [PATCH 66/75] table-defs: unused import --- src/blogify/backend/database/Tables.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/blogify/backend/database/Tables.kt b/src/blogify/backend/database/Tables.kt index c784d185..4b901767 100644 --- a/src/blogify/backend/database/Tables.kt +++ b/src/blogify/backend/database/Tables.kt @@ -15,7 +15,6 @@ import blogify.backend.services.models.Service import io.ktor.application.ApplicationCall import io.ktor.http.ContentType -import org.jetbrains.exposed.sql.Column import org.jetbrains.exposed.sql.ReferenceOption.* import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.Table From 6027a7d38da4fca1b2cee2d9c350d7d8070f7c1b Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Mon, 11 Nov 2019 14:59:28 -0500 Subject: [PATCH 67/75] validation: fixed article title regex --- src/blogify/backend/resources/Article.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blogify/backend/resources/Article.kt b/src/blogify/backend/resources/Article.kt index f98844bc..bf591975 100644 --- a/src/blogify/backend/resources/Article.kt +++ b/src/blogify/backend/resources/Article.kt @@ -31,7 +31,7 @@ import java.util.* property = "uuid" ) data class Article ( - val title: @check("^[^\\p{C}]+$") String, + val title: @check("^.{0,512}") String, val createdAt: Long = Date().time, From 0fc92940c93a035001132e2072cf665bdb494118 Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Mon, 11 Nov 2019 15:54:07 -0500 Subject: [PATCH 68/75] optimization: don't fetch user profile more than once for UUID --- .../frontend/src/app/services/article/article.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/blogify/frontend/src/app/services/article/article.service.ts b/src/blogify/frontend/src/app/services/article/article.service.ts index b683b29b..40aa2518 100644 --- a/src/blogify/frontend/src/app/services/article/article.service.ts +++ b/src/blogify/frontend/src/app/services/article/article.service.ts @@ -12,9 +12,9 @@ export class ArticleService { constructor(private httpClient: HttpClient, private authService: AuthService) {} private async fetchUserObjects(articles: Article[]): Promise { - const userUUIDs = articles + const userUUIDs = new Set([...articles .filter (it => typeof it.createdBy === 'string') - .map (it => it.createdBy); + .map (it => it.createdBy)]); // Converting to a Set makes sure a single UUID is not fetched more than once const userObjects = await Promise.all ( [...userUUIDs].map(it => this.authService.fetchUser(it)) ); From d97572666047735b9e19124b4e4f611de2e8253b Mon Sep 17 00:00:00 2001 From: Benjamin Dupont Date: Mon, 11 Nov 2019 16:39:06 -0500 Subject: [PATCH 69/75] search: move some typesense stuff to separate file --- src/blogify/backend/Application.kt | 15 ++----- src/blogify/backend/database/Database.kt | 3 ++ src/blogify/backend/search/Typesense.kt | 52 ++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 src/blogify/backend/search/Typesense.kt diff --git a/src/blogify/backend/Application.kt b/src/blogify/backend/Application.kt index e123c615..bb2488ca 100644 --- a/src/blogify/backend/Application.kt +++ b/src/blogify/backend/Application.kt @@ -17,6 +17,7 @@ import blogify.backend.routes.auth import blogify.backend.database.handling.query import blogify.backend.resources.models.Resource import blogify.backend.routes.static +import blogify.backend.search.Typesense import blogify.backend.util.SinglePageApplication import blogify.backend.util.TYPESENSE_API_KEY @@ -199,18 +200,8 @@ fun Application.mainModule(@Suppress("UNUSED_PARAMETER") testing: Boolean = fals "default_sorting_field": "dsf_jank" }""".trimIndent() - HttpClient().use { client -> - client.post { - url("http://ts:8108/collections") - body = TextContent(articleJson, contentType = ContentType.Application.Json) - header("X-TYPESENSE-API-KEY", TYPESENSE_API_KEY) - }.also { println(it) } - client.post { - url("http://ts:8108/collections") - body = TextContent(userJson, contentType = ContentType.Application.Json) - header("X-TYPESENSE-API-KEY", TYPESENSE_API_KEY) - }.also { println(it) } - } + Typesense.submitResourceTemplate(articleJson) + Typesense.submitResourceTemplate(userJson) } }} diff --git a/src/blogify/backend/database/Database.kt b/src/blogify/backend/database/Database.kt index f3c5a11d..b0675b71 100644 --- a/src/blogify/backend/database/Database.kt +++ b/src/blogify/backend/database/Database.kt @@ -7,6 +7,9 @@ import com.zaxxer.hikari.HikariDataSource import org.jetbrains.exposed.sql.Database +/** + * Meta object regrouping setup and utility functions for PostgreSQL. + */ object Database { lateinit var instance: Database diff --git a/src/blogify/backend/search/Typesense.kt b/src/blogify/backend/search/Typesense.kt new file mode 100644 index 00000000..357275eb --- /dev/null +++ b/src/blogify/backend/search/Typesense.kt @@ -0,0 +1,52 @@ +package blogify.backend.search + +import io.ktor.client.HttpClient +import io.ktor.client.features.json.JacksonSerializer +import io.ktor.client.features.json.JsonFeature +import io.ktor.client.request.header +import io.ktor.client.request.post +import io.ktor.client.request.url +import io.ktor.http.ContentType +import io.ktor.http.content.TextContent + +/** + * Meta object regrouping setup and utility functions for the Typesense search engine. + */ +object Typesense { + + /** + * Typesense REST API URL + */ + private const val TYPESENSE_URL = "http://ts:8108" + + /** + * Typesense API key HTTP header string + */ + private const val TYPESENSE_API_KEY_HEADER = "X-TYPESENSE-API-KEY" + + /** + * Typesense API key + */ + private const val TYPESENSE_API_KEY = "Hu52dwsas2AdxdE" + + private val typesenseClient = HttpClient { install(JsonFeature) { serializer = JacksonSerializer(); } } + + /** + * Uploads a document template to the Typesense REST API + * + * @param template the document template, in JSON format. + * See the [typesense docs](https://typesense.org/docs/0.11.0/api/#create-collection) for more info. + * + * @author Benjozork + */ + suspend fun submitResourceTemplate(template: String) { + typesenseClient.use { client -> + client.post { + url("$TYPESENSE_URL/collections") + body = TextContent(template, contentType = ContentType.Application.Json) + header(TYPESENSE_API_KEY_HEADER, TYPESENSE_API_KEY) + }.also { println(it) } + } + } + +} \ No newline at end of file From 78c32c5514e555bb4c6d39dce65c17c583d88ca6 Mon Sep 17 00:00:00 2001 From: Hamza Date: Tue, 12 Nov 2019 19:39:36 +0500 Subject: [PATCH 70/75] Get rid of useless static routes --- src/blogify/backend/routes/StaticRoutes.kt | 34 ---------------------- 1 file changed, 34 deletions(-) diff --git a/src/blogify/backend/routes/StaticRoutes.kt b/src/blogify/backend/routes/StaticRoutes.kt index 4261c32b..88759856 100644 --- a/src/blogify/backend/routes/StaticRoutes.kt +++ b/src/blogify/backend/routes/StaticRoutes.kt @@ -28,15 +28,6 @@ import com.github.kittinunf.result.coroutines.map fun Route.static() { - post("/testupload/{uuid}") { - uploadToResource ( - fetch = UserService::get, - modify = { r, h -> r.copy(profilePicture = h) }, - update = UserService::update, - authPredicate = { user, manipulated -> user eqr manipulated } - ) - } - get("/get/{uploadableId}") { pipeline("uploadableId") { (uploadableId) -> @@ -54,29 +45,4 @@ fun Route.static() { } - delete("/delete/{uploadableId}") { - - val doDelete: CallPipeLineFunction = { pipeline("uploadableId") { (uploadableId) -> - // TODO: None of this should be executed unless the owner is logged in. Fix that - // not-so VERY TEMP - val handle = query { - Uploadables.select { Uploadables.fileId eq uploadableId }.single() - }.map { Uploadables.convert(call, it).get() }.get() - - // Delete in DB - query { - Uploadables.deleteWhere { Uploadables.fileId eq uploadableId } - }.failure { pipelineError(HttpStatusCode.InternalServerError, "couldn't delete static resource from db") } - - // Delete in FS - if (StaticFileHandler.deleteStaticResource(handle)) { - call.respond(HttpStatusCode.OK) - } else pipelineError(HttpStatusCode.InternalServerError, "couldn't delete static resource file") - - } } - - handleAuthentication("resourceDelete", { _ -> true }, doDelete) - - } - } From 8d8a30b6ee3100053d39c766558b4080c14623d9 Mon Sep 17 00:00:00 2001 From: Hamza Date: Tue, 12 Nov 2019 19:42:06 +0500 Subject: [PATCH 71/75] Fix delete user endpoint --- src/blogify/backend/routes/users/UserRoutes.kt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/blogify/backend/routes/users/UserRoutes.kt b/src/blogify/backend/routes/users/UserRoutes.kt index 899123d5..cbe8dca9 100644 --- a/src/blogify/backend/routes/users/UserRoutes.kt +++ b/src/blogify/backend/routes/users/UserRoutes.kt @@ -9,11 +9,15 @@ import blogify.backend.resources.slicing.slice import blogify.backend.routes.handling.* import blogify.backend.services.UserService import blogify.backend.services.models.Service +import blogify.backend.util.TYPESENSE_API_KEY import io.ktor.application.call import io.ktor.client.HttpClient import io.ktor.client.features.json.JsonFeature +import io.ktor.client.request.delete import io.ktor.client.request.get +import io.ktor.client.request.header +import io.ktor.client.request.url import io.ktor.http.HttpStatusCode import io.ktor.response.respond import io.ktor.routing.* @@ -34,7 +38,19 @@ fun Route.users() { } delete("/{uuid}") { - deleteWithId(UserService::get, UserService::delete) + deleteWithId( + UserService::get, + UserService::delete, + authPredicate = { user, manipulated -> user eqr manipulated }, + doAfter = {id -> + HttpClient().use { client -> + client.delete { + url("http://ts:8108/collections/users/documents/$id") + header("X-TYPESENSE-API-KEY", TYPESENSE_API_KEY) + }.also { println(it) } + } + } + ) } patch("/{uuid}") { From f12b09a7841eb2fd3b7df446906a9ebd2df5e42d Mon Sep 17 00:00:00 2001 From: Hamza Date: Tue, 12 Nov 2019 19:48:33 +0500 Subject: [PATCH 72/75] `filtering-menu` with its filtering menu works text isn't shown --- .../show-all-articles/show-all-articles.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html index 03a4893e..c361482f 100644 --- a/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html +++ b/src/blogify/frontend/src/app/shared/components/show-all-articles/show-all-articles.component.html @@ -38,7 +38,7 @@

{{showingSearchResults ? '

- +
From 81bd5ed6c8948dc5bbe44cf75a9610f5c2a1a8d1 Mon Sep 17 00:00:00 2001 From: Hamza Date: Wed, 13 Nov 2019 20:11:14 +0500 Subject: [PATCH 73/75] Fix a couple of bruh moments --- src/blogify/backend/resources/User.kt | 3 +- .../app/components/login/login.component.ts | 65 +++++++++++-------- .../profile/settings/settings.component.html | 2 +- 3 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/blogify/backend/resources/User.kt b/src/blogify/backend/resources/User.kt index 77d06b24..125eab62 100644 --- a/src/blogify/backend/resources/User.kt +++ b/src/blogify/backend/resources/User.kt @@ -1,6 +1,5 @@ package blogify.backend.resources -import blogify.backend.annotations.check import blogify.backend.resources.models.Resource import blogify.backend.resources.static.models.StaticResourceHandle import blogify.backend.annotations.noslice @@ -22,7 +21,7 @@ data class User ( @noslice val password: String, // IMPORTANT : DO NOT EVER REMOVE THIS ANNOTATION ! val name: String, val email: String, - val profilePicture: @type("image/png") StaticResourceHandle, + val profilePicture: @type("image/*") StaticResourceHandle, override val uuid: UUID = UUID.randomUUID() ) : Resource(uuid) diff --git a/src/blogify/frontend/src/app/components/login/login.component.ts b/src/blogify/frontend/src/app/components/login/login.component.ts index 8861927d..41f8490d 100644 --- a/src/blogify/frontend/src/app/components/login/login.component.ts +++ b/src/blogify/frontend/src/app/components/login/login.component.ts @@ -27,42 +27,51 @@ export class LoginComponent implements OnInit { } async login() { - this.authService.login(this.loginCredentials).then(async token => { + this.authService.login(this.loginCredentials) + .then(async token => { + console.log(token); - console.log(token); + const uuid = await this.authService.userUUID; + this.user = await this.authService.userProfile; - const uuid = await this.authService.userUUID; - this.user = await this.authService.userProfile; + console.log('LOGIN ->'); + console.log(uuid); + console.log(this.user); + console.log(this.loginCredentials); + console.log(this.authService.userToken); + console.log(this.redirectTo); - console.log('LOGIN ->'); - console.log(uuid); - console.log(this.user); - console.log(this.loginCredentials); - console.log(this.authService.userToken); - console.log(this.redirectTo); - - if (this.redirectTo) { - await this.router.navigateByUrl(this.redirectTo); - } else { - await this.router.navigateByUrl('/home'); - } - }); + if (this.redirectTo) { + await this.router.navigateByUrl(this.redirectTo); + } else { + await this.router.navigateByUrl('/home'); + } + }) + .catch((error) => { + alert("An error occurred during login"); + console.error(`[login]: ${error}`) + }); } async register() { - this.authService.register(this.registerCredentials).then(async user => { - this.user = user; + this.authService.register(this.registerCredentials) + .then(async user => { + this.user = user; - console.log('REGISTER ->'); - console.log(this.user); - console.log(this.registerCredentials); + console.log('REGISTER ->'); + console.log(this.user); + console.log(this.registerCredentials); - if (this.redirectTo) { - await this.router.navigateByUrl(this.redirectTo); - } else { - await this.router.navigateByUrl('/home'); - } - }); + if (this.redirectTo) { + await this.router.navigateByUrl(this.redirectTo); + } else { + await this.router.navigateByUrl('/home'); + } + }) + .catch((error) => { + alert("An error occurred during login"); + console.error(`[register]: ${error}`) + }); } diff --git a/src/blogify/frontend/src/app/components/profile/profile/settings/settings.component.html b/src/blogify/frontend/src/app/components/profile/profile/settings/settings.component.html index 7336d14c..1040bc81 100644 --- a/src/blogify/frontend/src/app/components/profile/profile/settings/settings.component.html +++ b/src/blogify/frontend/src/app/components/profile/profile/settings/settings.component.html @@ -2,4 +2,4 @@

Settings

- + From 2ea12bc14651d374913703a075fbf02f33f9400c Mon Sep 17 00:00:00 2001 From: Hamza Date: Wed, 13 Nov 2019 20:18:47 +0500 Subject: [PATCH 74/75] Bump version --- Dockerfile | 4 ++-- build.gradle.kts | 2 +- src/blogify/backend/Application.kt | 10 ++-------- src/blogify/frontend/src/app/app.component.html | 2 +- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/Dockerfile b/Dockerfile index 332c3d74..6b181595 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,9 +2,9 @@ FROM openjdk:10-jre WORKDIR /var/server/ -ADD build/dist/jar/blogify-PRX2-all.jar . +ADD build/dist/jar/blogify-0.1.0-all.jar . EXPOSE 8080 EXPOSE 5005 -CMD ["java", "-server", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", "-XX:+UseStringDeduplication", "-jar", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", "blogify-PRX2-all.jar"] \ No newline at end of file +CMD ["java", "-server", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", "-XX:+UseStringDeduplication", "-jar", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", "blogify-0.1.0-all.jar"] \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 566b20a8..fd794301 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,7 +17,7 @@ plugins { } group = "blogify" -version = "PRX2" +version = "0.1.0" application { mainClassName = "io.ktor.server.netty.EngineMain" diff --git a/src/blogify/backend/Application.kt b/src/blogify/backend/Application.kt index bb2488ca..09543730 100644 --- a/src/blogify/backend/Application.kt +++ b/src/blogify/backend/Application.kt @@ -19,7 +19,6 @@ import blogify.backend.resources.models.Resource import blogify.backend.routes.static import blogify.backend.search.Typesense import blogify.backend.util.SinglePageApplication -import blogify.backend.util.TYPESENSE_API_KEY import io.ktor.application.call import io.ktor.features.Compression @@ -28,11 +27,6 @@ import io.ktor.response.respondRedirect import io.ktor.routing.get import io.ktor.application.Application import io.ktor.application.install -import io.ktor.client.HttpClient -import io.ktor.client.request.header -import io.ktor.client.request.post -import io.ktor.client.request.url -import io.ktor.content.TextContent import io.ktor.features.CachingHeaders import io.ktor.features.CallLogging import io.ktor.features.ContentNegotiation @@ -50,7 +44,7 @@ import kotlinx.coroutines.runBlocking import org.slf4j.event.Level -const val version = "PRX4" +const val version = "0.1.0" const val asciiLogo = """ __ __ _ ____ @@ -112,7 +106,7 @@ fun Application.mainModule(@Suppress("UNUSED_PARAMETER") testing: Boolean = fals // Default headers install(DefaultHeaders) { - header("Server", "blogify-core PRX4") + header("Server", "blogify-core $version") header("X-Powered-By", "Ktor 1.2.3") } diff --git a/src/blogify/frontend/src/app/app.component.html b/src/blogify/frontend/src/app/app.component.html index 408e7ff7..109dafc7 100644 --- a/src/blogify/frontend/src/app/app.component.html +++ b/src/blogify/frontend/src/app/app.component.html @@ -2,7 +2,7 @@
- blogify-core PRX4 + blogify-core v0.1.0 Copyright ©2019 the Blogify contributors Licensed under the GNU General Public License Version 3 (GPLv3) Source code From 357b41ed44568b966f9c33f3e330a3d8dc1e123b Mon Sep 17 00:00:00 2001 From: Hamza Date: Wed, 13 Nov 2019 21:03:53 +0500 Subject: [PATCH 75/75] Comment out dummy buttons --- .../comment/create-comment/create-comment.component.html | 2 +- .../frontend/src/app/components/navbar/navbar.component.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/blogify/frontend/src/app/components/comment/create-comment/create-comment.component.html b/src/blogify/frontend/src/app/components/comment/create-comment/create-comment.component.html index 0c0595b0..8c8895d2 100644 --- a/src/blogify/frontend/src/app/components/comment/create-comment/create-comment.component.html +++ b/src/blogify/frontend/src/app/components/comment/create-comment/create-comment.component.html @@ -3,7 +3,7 @@
- + {{replyError}}
diff --git a/src/blogify/frontend/src/app/components/navbar/navbar.component.html b/src/blogify/frontend/src/app/components/navbar/navbar.component.html index 15c71f9d..45dde576 100644 --- a/src/blogify/frontend/src/app/components/navbar/navbar.component.html +++ b/src/blogify/frontend/src/app/components/navbar/navbar.component.html @@ -32,9 +32,9 @@ - +