diff --git a/app/Http/Controllers/NewsroomController.php b/app/Http/Controllers/NewsroomController.php
new file mode 100644
index 000000000..3eadc2aa0
--- /dev/null
+++ b/app/Http/Controllers/NewsroomController.php
@@ -0,0 +1,92 @@
+latest()->paginate(9);
+ } else {
+ $posts = Newsroom::whereNotNull('published_at')
+ ->whereAuthOnly(false)
+ ->latest()
+ ->paginate(3);
+ }
+ return view('site.news.home', compact('posts'));
+ }
+
+ public function show(Request $request, $year, $month, $slug)
+ {
+ $post = Newsroom::whereNotNull('published_at')
+ ->whereSlug($slug)
+ ->whereYear('published_at', $year)
+ ->whereMonth('published_at', $month)
+ ->firstOrFail();
+ abort_if($post->auth_only && !$request->user(), 404);
+ return view('site.news.post.show', compact('post'));
+ }
+
+ public function search(Request $request)
+ {
+ $this->validate($request, [
+ 'q' => 'nullable'
+ ]);
+ }
+
+ public function archive(Request $request)
+ {
+ return view('site.news.archive.index');
+ }
+
+ public function timelineApi(Request $request)
+ {
+ abort_if(!Auth::check(), 404);
+
+ $key = 'newsroom:read:profileid:' . $request->user()->profile_id;
+ $read = Redis::smembers($key);
+
+ $posts = Newsroom::whereNotNull('published_at')
+ ->whereShowTimeline(true)
+ ->whereNotIn('id', $read)
+ ->orderBy('id', 'desc')
+ ->take(9)
+ ->get()
+ ->map(function($post) {
+ return [
+ 'id' => $post->id,
+ 'title' => Str::limit($post->title, 25),
+ 'summary' => $post->summary,
+ 'url' => $post->show_link ? $post->permalink() : null,
+ 'published_at' => $post->published_at->format('F m, Y')
+ ];
+ });
+ return response()->json($posts, 200, [], JSON_PRETTY_PRINT);
+ }
+
+ public function markAsRead(Request $request)
+ {
+ abort_if(!Auth::check(), 404);
+
+ $this->validate($request, [
+ 'id' => 'required|integer|min:1'
+ ]);
+
+ $news = Newsroom::whereNotNull('published_at')
+ ->findOrFail($request->input('id'));
+
+ $key = 'newsroom:read:profileid:' . $request->user()->profile_id;
+
+ Redis::sadd($key, $news->id);
+
+ return response()->json(['code' => 200]);
+ }
+}
diff --git a/app/Newsroom.php b/app/Newsroom.php
new file mode 100644
index 000000000..ad92d7b43
--- /dev/null
+++ b/app/Newsroom.php
@@ -0,0 +1,22 @@
+published_at->year;
+ $month = $this->published_at->format('m');
+ $slug = $this->slug;
+
+ return url("/site/newsroom/{$year}/{$month}/{$slug}");
+ }
+}
diff --git a/database/migrations/2019_12_10_023604_create_newsroom_table.php b/database/migrations/2019_12_10_023604_create_newsroom_table.php
new file mode 100644
index 000000000..2651d5c4d
--- /dev/null
+++ b/database/migrations/2019_12_10_023604_create_newsroom_table.php
@@ -0,0 +1,45 @@
+bigIncrements('id');
+ $table->bigInteger('user_id')->unsigned()->nullable();
+ $table->string('header_photo_url')->nullable();
+ $table->string('title')->nullable();
+ $table->string('slug')->nullable()->unique()->index();
+ $table->string('category')->default('update');
+ $table->text('summary')->nullable();
+ $table->text('body')->nullable();
+ $table->text('body_rendered')->nullable();
+ $table->string('link')->nullable();
+ $table->boolean('force_modal')->default(false);
+ $table->boolean('show_timeline')->default(false);
+ $table->boolean('show_link')->default(false);
+ $table->boolean('auth_only')->default(true);
+ $table->timestamp('published_at')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('site_news');
+ }
+}
diff --git a/resources/assets/js/components/AnnouncementsCard.vue b/resources/assets/js/components/AnnouncementsCard.vue
new file mode 100644
index 000000000..2310ceffd
--- /dev/null
+++ b/resources/assets/js/components/AnnouncementsCard.vue
@@ -0,0 +1,155 @@
+
+
+ {{announcement.summary}}
+
+ Read more
+
+
+
+
+
+
+
+
+
+
+
+
+
Archive here
+{{$post->category}}
+{{$post->published_at->format('F d, Y')}}
+{{$post->title}}
++ {{$post->summary}} +
++ {!!$post->body!!} +
+