blog777 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +50 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/blog_manifest.js +1 -0
  6. data/app/assets/stylesheets/blog/application.css +15 -0
  7. data/app/controllers/blog/application_controller.rb +4 -0
  8. data/app/controllers/blog/articles_controller.rb +54 -0
  9. data/app/controllers/blog/graphql_controller.rb +52 -0
  10. data/app/controllers/blog/tags_controller.rb +53 -0
  11. data/app/graphql/blog/mutations/base_mutation.rb +10 -0
  12. data/app/graphql/blog/schema.rb +28 -0
  13. data/app/graphql/blog/types/article_type.rb +11 -0
  14. data/app/graphql/blog/types/base_argument.rb +6 -0
  15. data/app/graphql/blog/types/base_enum.rb +6 -0
  16. data/app/graphql/blog/types/base_field.rb +7 -0
  17. data/app/graphql/blog/types/base_input_object.rb +7 -0
  18. data/app/graphql/blog/types/base_interface.rb +9 -0
  19. data/app/graphql/blog/types/base_object.rb +7 -0
  20. data/app/graphql/blog/types/base_scalar.rb +6 -0
  21. data/app/graphql/blog/types/base_union.rb +6 -0
  22. data/app/graphql/blog/types/mutation_type.rb +11 -0
  23. data/app/graphql/blog/types/query_type.rb +38 -0
  24. data/app/graphql/blog/types/tag_type.rb +9 -0
  25. data/app/helpers/blog/application_helper.rb +4 -0
  26. data/app/helpers/blog/articles_helper.rb +4 -0
  27. data/app/helpers/blog/tags_helper.rb +4 -0
  28. data/app/jobs/blog/application_job.rb +4 -0
  29. data/app/mailers/blog/application_mailer.rb +6 -0
  30. data/app/models/blog/application_record.rb +5 -0
  31. data/app/models/blog/article.rb +52 -0
  32. data/app/models/blog/tag.rb +5 -0
  33. data/app/uploaders/blog/image_uploader.rb +82 -0
  34. data/app/views/layouts/blog/application.html.erb +15 -0
  35. data/config/routes.rb +8 -0
  36. data/db/migrate/20230124071425_create_blog_articles.rb +25 -0
  37. data/lib/blog/engine.rb +12 -0
  38. data/lib/blog/version.rb +3 -0
  39. data/lib/blog.rb +10 -0
  40. data/lib/tasks/blog_tasks.rake +4 -0
  41. metadata +182 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fd94c6633b19b6fd264163426dbfb5bd452f482817b5ba1d7c02c6eae6f9a80c
4
+ data.tar.gz: a6025e452f05cf557c76cc1a8f095c230c2404b8ec4085b6d12ff77ccbabe63f
5
+ SHA512:
6
+ metadata.gz: 1b29de38d18e65ac9ba3206d00b7169994865288b5615df2775d2c4bb7d64367f0538ea49fabc472803dea365a02d6ff404edbe537c1dea9f0705a6570aab9c6
7
+ data.tar.gz: 5d930898834dab750c561b5599ad1874adf6d8627ff078717d01e1901080dd6b9a14ee916dd531a9bd1ab3123fc9fcd5a2fedada7f92a732ff071b45049afaf2
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2023 Eldar
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # Блог
2
+ Компонент "Блог" для проекта, который имеет теги и статьи по ним.
3
+
4
+ ## Установка гема
5
+ Добавьте эту строку в Gemfile вашего приложения:
6
+
7
+ ```ruby
8
+ gem "blog", git: "git@gitlab.telega.in:components-backend/Blog.git"
9
+ ```
10
+
11
+ А затем выполните:
12
+ ```bash
13
+ $ bundle
14
+ ```
15
+
16
+ Или установите его самостоятельно как:
17
+ ```bash
18
+ $ gem install blog --source https://gitlab.telega.in/components-backend/Blog.git
19
+ ```
20
+
21
+ ## Backend
22
+ 1. Укажите путь к API в config/routes.rb:
23
+ ```ruby
24
+ mount Blog::Engine, at: '/blog'
25
+ ```
26
+ 2. Выполните команду:
27
+ ```bash
28
+ $ rails blog:install:migrations
29
+ ```
30
+ 3. Запустите миграции:
31
+ ```bash
32
+ $ rails db:migrate
33
+ ```
34
+
35
+ Для Active Admin:
36
+ 1. Установите сущность для Blog::Article
37
+ 2. Установите сущность для Blog::Tag
38
+
39
+ ## Frontend (API)
40
+ Используемые сущности: Article, Tag.
41
+ Доступны два варианта обращения к этим сущностям:
42
+ 1. REST Full запросы по articles, tags через /blog. Примеры: /blog/articles, /blog/tags
43
+ 2. GraphQl post-запросы к адресу /graphql. Документация и выполнение запросов доступны по url: /blog/graphiql
44
+
45
+
46
+ ## Contributing
47
+ Contribution directions go here.
48
+
49
+ ## License
50
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/blog .css
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,4 @@
1
+ module Blog
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,54 @@
1
+ module Blog
2
+ class ArticlesController < ApplicationController
3
+ before_action :set_article, only: %i[ show update destroy ]
4
+
5
+ # GET /articles
6
+ def index
7
+ render json: Article.filters(params)
8
+ end
9
+
10
+ # GET /articles/1
11
+ def show
12
+ @article.increment!(:view_count)
13
+ render json: @article
14
+ end
15
+
16
+ # POST /articles
17
+ def create
18
+ article = Article.new(article_params)
19
+
20
+ if article.save
21
+ render json: article, status: :created
22
+ else
23
+ render json: article.errors, status: :unprocessable_entity
24
+ end
25
+ end
26
+
27
+ # PATCH/PUT /articles/1
28
+ def update
29
+ if @article.update(article_params)
30
+ render json: @article
31
+ else
32
+ render json: @article.errors, status: :unprocessable_entity
33
+ end
34
+ end
35
+
36
+ # DELETE /articles/1
37
+ def destroy
38
+ @article.destroy
39
+ render json: { success: true }
40
+ end
41
+
42
+ private
43
+
44
+ # Use callbacks to share common setup or constraints between actions.
45
+ def set_article
46
+ @article = Article.find(params[:id])
47
+ end
48
+
49
+ # Only allow a list of trusted parameters through.
50
+ def article_params
51
+ params.require(:article).permit(:title, :description, :image, :tag_id, :archived, :active)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,52 @@
1
+ module Blog
2
+ class GraphqlController < ApplicationController
3
+ # If accessing from outside this domain, nullify the session
4
+ # This allows for outside API access while preventing CSRF attacks,
5
+ # but you'll have to authenticate your user separately
6
+ # protect_from_forgery with: :null_session
7
+
8
+ def execute
9
+ variables = prepare_variables(params[:variables])
10
+ query = params[:query]
11
+ operation_name = params[:operationName]
12
+ context = {
13
+ # Query context goes here, for example:
14
+ # current_user: current_user,
15
+ }
16
+ result = Schema.execute(query, variables: variables, context: context, operation_name: operation_name)
17
+ render json: result
18
+ rescue StandardError => e
19
+ raise e unless Rails.env.development?
20
+ handle_error_in_development(e)
21
+ end
22
+
23
+ private
24
+
25
+ # Handle variables in form data, JSON body, or a blank value
26
+ def prepare_variables(variables_param)
27
+ case variables_param
28
+ when String
29
+ if variables_param.present?
30
+ JSON.parse(variables_param) || {}
31
+ else
32
+ {}
33
+ end
34
+ when Hash
35
+ variables_param
36
+ when ActionController::Parameters
37
+ variables_param.to_unsafe_hash # GraphQL-Ruby will validate name and type of incoming variables.
38
+ when nil
39
+ {}
40
+ else
41
+ raise ArgumentError, "Unexpected parameter: #{variables_param}"
42
+ end
43
+ end
44
+
45
+ def handle_error_in_development(e)
46
+ logger.error e.message
47
+ logger.error e.backtrace.join("\n")
48
+
49
+ render json: { errors: [{ message: e.message, backtrace: e.backtrace }], data: {} }, status: 500
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,53 @@
1
+ module Blog
2
+ class TagsController < ApplicationController
3
+ before_action :set_tag, only: %i[ show update destroy ]
4
+
5
+ # GET /tags
6
+ def index
7
+ render json: Tag.all
8
+ end
9
+
10
+ # GET /tags/1
11
+ def show
12
+ render json: @tag
13
+ end
14
+
15
+ # POST /tags
16
+ def create
17
+ @tag = Tag.new(tag_params)
18
+
19
+ if @tag.save
20
+ render json: @tag, status: :created
21
+ else
22
+ render json: @tag.errors, status: :unprocessable_entity
23
+ end
24
+ end
25
+
26
+ # PATCH/PUT /tags/1
27
+ def update
28
+ if @tag.update(tag_params)
29
+ render json: @tag
30
+ else
31
+ render json: @tag.errors, status: :unprocessable_entity
32
+ end
33
+ end
34
+
35
+ # DELETE /tags/1
36
+ def destroy
37
+ @tag.destroy
38
+ render json: { success: true }
39
+ end
40
+
41
+ private
42
+
43
+ # Use callbacks to share common setup or constraints between actions.
44
+ def set_tag
45
+ @tag = Tag.find(params[:id])
46
+ end
47
+
48
+ # Only allow a list of trusted parameters through.
49
+ def tag_params
50
+ params.require(:tag).permit(:title, :arichived)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,10 @@
1
+ module Blog
2
+ module Mutations
3
+ class BaseMutation < GraphQL::Schema::RelayClassicMutation
4
+ argument_class Types::BaseArgument
5
+ field_class Types::BaseField
6
+ input_object_class Types::BaseInputObject
7
+ object_class Types::BaseObject
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,28 @@
1
+ module Blog
2
+ class Schema < GraphQL::Schema
3
+ mutation(Types::MutationType)
4
+ query(Types::QueryType)
5
+
6
+ # For batch-loading (see https://graphql-ruby.org/dataloader/overview.html)
7
+ use GraphQL::Dataloader
8
+
9
+ # GraphQL-Ruby calls this when something goes wrong while running a query:
10
+ def self.type_error(err, context)
11
+ # if err.is_a?(GraphQL::InvalidNullError)
12
+ # # report to your bug tracker here
13
+ # return nil
14
+ # end
15
+ super
16
+ end
17
+
18
+ # Union and Interface Resolution
19
+ def self.resolve_type(abstract_type, obj, ctx)
20
+ # TODO: Implement this method
21
+ # to return the correct GraphQL object type for `obj`
22
+ raise(GraphQL::RequiredImplementationMissingError)
23
+ end
24
+
25
+ # Stop validating when it encounters this many errors:
26
+ validate_max_errors(100)
27
+ end
28
+ end
@@ -0,0 +1,11 @@
1
+ module Blog
2
+ module Types
3
+ class ArticleType < Types::BaseObject
4
+ description "A blog article"
5
+ field :id, ID, null: false
6
+ field :title, String, null: false
7
+ field :html_description, String, null: false
8
+ field :tag, TagType, null: false, description: "This post's tag"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ module Blog
2
+ module Types
3
+ class BaseArgument < GraphQL::Schema::Argument
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Blog
2
+ module Types
3
+ class BaseEnum < GraphQL::Schema::Enum
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ module Blog
2
+ module Types
3
+ class BaseField < GraphQL::Schema::Field
4
+ argument_class Types::BaseArgument
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Blog
2
+ module Types
3
+ class BaseInputObject < GraphQL::Schema::InputObject
4
+ argument_class Types::BaseArgument
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module Blog
2
+ module Types
3
+ module BaseInterface
4
+ include GraphQL::Schema::Interface
5
+
6
+ field_class Types::BaseField
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module Blog
2
+ module Types
3
+ class BaseObject < GraphQL::Schema::Object
4
+ field_class Types::BaseField
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ module Blog
2
+ module Types
3
+ class BaseScalar < GraphQL::Schema::Scalar
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Blog
2
+ module Types
3
+ class BaseUnion < GraphQL::Schema::Union
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,11 @@
1
+ module Blog
2
+ module Types
3
+ class MutationType < Types::BaseObject
4
+ # TODO: remove me
5
+ field :test_field, String, null: false, description: "An example field added by the generator"
6
+ def test_field
7
+ "Hello World"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,38 @@
1
+ module Blog
2
+ module Types
3
+ class QueryType < Types::BaseObject
4
+
5
+ field :articles, [ArticleType], null: false, description: "Вывод всех постов" do
6
+ argument :page, Int, required: false, description: 'Номер страницы'
7
+ argument :tag_id, Int, required: false, description: 'ID тега'
8
+ argument :query, String, required: false, description: 'Поисковый запрос'
9
+ end
10
+
11
+ def articles(page: 1, tag_id: nil, query: nil)
12
+ Article.preload(:tag).filters({page: page, tag_id: tag_id, query: query})
13
+ end
14
+
15
+ field :article, ArticleType, "Поиск поста по ID" do
16
+ argument :id, ID
17
+ end
18
+
19
+ def article(id:)
20
+ Article.find(id)
21
+ end
22
+
23
+ field :tags, [TagType], null: false, description: "Вывод всех тегов"
24
+ def tags
25
+ Tag.all
26
+ end
27
+
28
+ field :tag, TagType, "Поиск тега по ID" do
29
+ argument :id, ID
30
+ end
31
+
32
+ def tag(id:)
33
+ Tag.find(id)
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,9 @@
1
+ module Blog
2
+ module Types
3
+ class TagType < Types::BaseObject
4
+ description "A blog tag"
5
+ field :id, ID, null: false
6
+ field :title, String, null: false
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ module Blog
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Blog
2
+ module ArticlesHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Blog
2
+ module TagsHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Blog
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module Blog
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Blog
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,52 @@
1
+ module Blog
2
+ class Article < ApplicationRecord
3
+ include PgSearch::Model
4
+
5
+ pg_search_scope :search,
6
+ against: {
7
+ title: 'A',
8
+ description: 'B'
9
+ },
10
+ using: {
11
+ tsearch: { prefix: true }
12
+ }
13
+
14
+ mount_uploader :image, Blog::ImageUploader
15
+
16
+ belongs_to :tag
17
+
18
+ validates :title, presence: true, length: { minimum: 2 , maximum: 250 }
19
+ validates :description, presence: true
20
+ validates :image, presence: true
21
+ validates :tag_id, presence: true
22
+ validates :slug, presence: true, uniqueness: true, format: { with: /\A[a-z0-9\-_]+\z/ }
23
+ validates :locale, presence: true
24
+
25
+ before_validation :set_slug
26
+ before_validation :set_locale
27
+ before_validation :clean_description
28
+
29
+ scope :filters, lambda { |params|
30
+ rel = where(archived: false)
31
+ rel = rel.where(tag_id: params[:tag_id]) if params[:tag_id].present?
32
+ rel = rel.search(params[:query]) if params[:query].present?
33
+ rel.order('created_at DESC').page(params[:page]).per(20)
34
+ }
35
+
36
+ private
37
+
38
+ def set_slug
39
+ text = slug.present? ? slug : title
40
+ self.slug = Translit.convert(text, :english).parameterize rescue nil
41
+ end
42
+
43
+ def set_locale
44
+ self.locale = locale || I18n.locale
45
+ end
46
+
47
+ def clean_description
48
+ self.description = Nokogiri::HTML(self.html_description).text
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,5 @@
1
+ module Blog
2
+ class Tag < ApplicationRecord
3
+ has_many :articles
4
+ end
5
+ end
@@ -0,0 +1,82 @@
1
+ module Blog
2
+ class ImageUploader < CarrierWave::Uploader::Base
3
+ include CarrierWave::MiniMagick
4
+
5
+ storage :file
6
+
7
+ ALLOWED_FORMAT_FILES = ['jpg', 'jpeg', 'png', 'gif']
8
+ MAX_FILE_SIZE = Rails.env.test? ? 0.5.megabytes : 5.megabytes
9
+ MIN_RESOLUTION = [500, 500] #height, width in pixels
10
+ MAX_QUALITY = 90 #%
11
+ MIN_QUALITY = 70 #%
12
+
13
+ def extension_allowlist
14
+ ALLOWED_FORMAT_FILES
15
+ end
16
+
17
+ def content_type_allowlist
18
+ ALLOWED_FORMAT_FILES.map {|ext| "image/#{ext}"}
19
+ end
20
+
21
+ def initialize a, b
22
+ @min_height = MIN_RESOLUTION[0]
23
+ @min_width = MIN_RESOLUTION[1]
24
+ @max_file_size = MAX_FILE_SIZE
25
+ @min_quality = MIN_QUALITY
26
+ @max_quality = MAX_QUALITY
27
+ @attribute = :images
28
+ super a, b
29
+ end
30
+
31
+ version :thumb do
32
+ process resize_to_fill: [300, 300]
33
+ end
34
+
35
+ process :check_file_size
36
+ process :check_resolution
37
+ process :quality
38
+
39
+ def check_file_size
40
+ return unless @max_file_size
41
+ return unless File.size?(current_path)&.> @max_file_size
42
+ model.errors.add @attribute, I18n.t('errors.messages.max_size_error',
43
+ max_size: @max_file_size.unmegabyte,
44
+ filename: original_filename)
45
+ end
46
+
47
+ def check_resolution
48
+ @img = ::MiniMagick::Image.open current_path
49
+ if (@max_height&.< @img.height) || (@max_width&.< @img.width)
50
+ model.errors.add @attribute, I18n.t('errors.messages.max_resolution', height: @max_height, width: @max_width)
51
+ end
52
+ if (@min_height&.> @img.height) || (@min_width&.> @img.width)
53
+ model.errors.add @attribute, I18n.t('errors.messages.min_resolution', height: @min_height, width: @min_width)
54
+ end
55
+ end
56
+
57
+ def quality
58
+ # Для маленьких файлов качество приближается к max (%), по мере роста размера файла, качество линейно
59
+ # падает. Для максимально разрешенного размера файла это значение составляет min (%)
60
+ image = MiniMagick::Image.open current_path
61
+ out_quality = (@max_quality - (@max_quality - @min_quality) * image.size / @max_file_size).round
62
+ if image.data['quality'].blank? || image.data['quality'] > out_quality
63
+ `mogrify -quality #{out_quality} #{current_path}`
64
+ end
65
+ rescue
66
+ nil
67
+ end
68
+
69
+ def filename
70
+ if original_filename
71
+ "#{model.class.name.downcase}.#{Digest::MD5.hexdigest(original_filename)[0..7]}.#{file.extension}"
72
+ end
73
+ end
74
+
75
+ def store_dir
76
+ return "test/files/db#{ENV['TEST_ENV_NUMBER']}/#{model.class.name.downcase}/#{model.id}" if Rails.env.test?
77
+ "files/blog/articles/#{model.id}"
78
+ end
79
+
80
+ CarrierWave::SanitizedFile.sanitize_regexp = /[^a-zA-Zа-яА-ЯёЁ0-9\.\-\+_]/u
81
+ end
82
+ end
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Blog</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "blog/application", media: "all" %>
9
+ </head>
10
+ <body>
11
+
12
+ <%= yield %>
13
+
14
+ </body>
15
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,8 @@
1
+ Blog::Engine.routes.draw do
2
+
3
+ mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: '/blog/graphql'
4
+
5
+ post '/graphql', to: 'graphql#execute'
6
+ resources :tags
7
+ resources :articles
8
+ end
@@ -0,0 +1,25 @@
1
+ class CreateBlogArticles < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :blog_tags do |t|
4
+ t.string :title
5
+ t.boolean :archived, null: false, default: false
6
+ t.timestamps
7
+ end
8
+
9
+ create_table :blog_articles do |t|
10
+ t.string :title, null: false
11
+ t.string :slug, null: false
12
+ t.text :html_description, null: false
13
+ t.text :description, null: false
14
+ t.string :image, null: false
15
+ t.belongs_to :tag, null: false, foreign_key: { to_table: :blog_tags }
16
+ t.integer :view_count, null: false, default: 0
17
+ t.boolean :archived, null: false, default: false
18
+ t.boolean :active, null: false, default: true
19
+ t.string :locale, null: false
20
+ t.timestamps
21
+ end
22
+
23
+ add_index :blog_articles, :slug, unique: true
24
+ end
25
+ end
@@ -0,0 +1,12 @@
1
+ require 'carrierwave'
2
+ require 'pg_search'
3
+ require 'kaminari'
4
+ require 'translit'
5
+ require 'graphql'
6
+ require 'graphiql/rails'
7
+
8
+ module Blog
9
+ class Engine < ::Rails::Engine
10
+ isolate_namespace Blog
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module Blog
2
+ VERSION = "0.1.0"
3
+ end
data/lib/blog.rb ADDED
@@ -0,0 +1,10 @@
1
+ require "blog/version"
2
+ require "blog/engine"
3
+
4
+ module Blog
5
+
6
+ # Your code goes here...
7
+
8
+ # mattr_accessor :article
9
+
10
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :blog do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,182 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: blog777
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Eldar
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-01-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 7.0.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 7.0.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: carrierwave
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: translit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pg_search
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: kaminari
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: graphql
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: graphiql-rails
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Description of AppComponent
112
+ email:
113
+ - eldarweb@mail.ru
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - MIT-LICENSE
119
+ - README.md
120
+ - Rakefile
121
+ - app/assets/config/blog_manifest.js
122
+ - app/assets/stylesheets/blog/application.css
123
+ - app/controllers/blog/application_controller.rb
124
+ - app/controllers/blog/articles_controller.rb
125
+ - app/controllers/blog/graphql_controller.rb
126
+ - app/controllers/blog/tags_controller.rb
127
+ - app/graphql/blog/mutations/base_mutation.rb
128
+ - app/graphql/blog/schema.rb
129
+ - app/graphql/blog/types/article_type.rb
130
+ - app/graphql/blog/types/base_argument.rb
131
+ - app/graphql/blog/types/base_enum.rb
132
+ - app/graphql/blog/types/base_field.rb
133
+ - app/graphql/blog/types/base_input_object.rb
134
+ - app/graphql/blog/types/base_interface.rb
135
+ - app/graphql/blog/types/base_object.rb
136
+ - app/graphql/blog/types/base_scalar.rb
137
+ - app/graphql/blog/types/base_union.rb
138
+ - app/graphql/blog/types/mutation_type.rb
139
+ - app/graphql/blog/types/query_type.rb
140
+ - app/graphql/blog/types/tag_type.rb
141
+ - app/helpers/blog/application_helper.rb
142
+ - app/helpers/blog/articles_helper.rb
143
+ - app/helpers/blog/tags_helper.rb
144
+ - app/jobs/blog/application_job.rb
145
+ - app/mailers/blog/application_mailer.rb
146
+ - app/models/blog/application_record.rb
147
+ - app/models/blog/article.rb
148
+ - app/models/blog/tag.rb
149
+ - app/uploaders/blog/image_uploader.rb
150
+ - app/views/layouts/blog/application.html.erb
151
+ - config/routes.rb
152
+ - db/migrate/20230124071425_create_blog_articles.rb
153
+ - lib/blog.rb
154
+ - lib/blog/engine.rb
155
+ - lib/blog/version.rb
156
+ - lib/tasks/blog_tasks.rake
157
+ homepage: https://gitlab.telega.in/components-backend/Blog
158
+ licenses:
159
+ - MIT
160
+ metadata:
161
+ allowed_push_host: https://rubygems.org
162
+ homepage_uri: https://gitlab.telega.in/components-backend/Blog
163
+ post_install_message:
164
+ rdoc_options: []
165
+ require_paths:
166
+ - lib
167
+ required_ruby_version: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - ">="
170
+ - !ruby/object:Gem::Version
171
+ version: '0'
172
+ required_rubygems_version: !ruby/object:Gem::Requirement
173
+ requirements:
174
+ - - ">="
175
+ - !ruby/object:Gem::Version
176
+ version: '0'
177
+ requirements: []
178
+ rubygems_version: 3.3.7
179
+ signing_key:
180
+ specification_version: 4
181
+ summary: Summary of AppComponent
182
+ test_files: []